Bug Summary

File:ui/GameUI.cpp
Warning:line 27402, column 40
Wrong iterator dereferenced

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -analyzer-opt-analyze-nested-blocks -analyzer-config-compatibility-mode true -analyzer-purge none -analyzer-checker core -analyzer-checker apiModeling -analyzer-checker unix -analyzer-checker deadcode -analyzer-checker cplusplus -analyzer-checker security.insecureAPI.UncheckedReturn -analyzer-checker security.insecureAPI.getpw -analyzer-checker security.insecureAPI.gets -analyzer-checker security.insecureAPI.mktemp -analyzer-checker security.insecureAPI.mkstemp -analyzer-checker security.insecureAPI.vfork -analyzer-checker nullability.NullPassedToNonnull -analyzer-checker nullability.NullReturnedFromNonnull -analyzer-checker STL_seki -analyzer-disable-checker deadcode -analyzer-config experimental-enable-naive-ctu-analysis=true -analyzer-config ctu-invocation-list=/usr/STLcheck/sekitest/Barony/build/csa-scan/invocations.yaml -analyzer-config suppress-null-return-paths=false -analyzer-config ctu-index-name=/usr/STLcheck/sekitest/Barony/build/csa-scan/externalDefMap.txt -analyzer-config aggressive-binary-operation-simplification=true -analyzer-config crosscheck-with-z3=true -analyzer-config ctu-dir=/usr/STLcheck/sekitest/Barony/build/csa-scan -w -ferror-limit 19 -Wno-pedantic -Wno-empty-body -Wno-string-plus-int -Wno-parentheses -Wno-format-security -Wno-multichar -Wno-inconsistent-missing-override -o /usr/STLcheck/sekitest/Barony/build/csa-scan/reports -disable-free -analyze -x c++ /usr/STLcheck/sekitest/Barony/src/ui/GameUI.cpp -target-cpu x86-64 -tune-cpu generic -triple x86_64-unknown-linux-gnu -resource-dir /usr/STLcheck/seki/build-release/lib/clang/15.0.0 -I /usr/include/SDL2 -isystem /usr/STLcheck/clang/include/clang/v1 -isystem /usr/STLcheck/seki/build-release/lib/clang/15.0.0/include -isystem /usr/local/include -isystem /usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -std=c++17 -fcxx-exceptions -fexceptions -fmath-errno -pic-level 2 -pic-is-pie -fvisibility default -fdeprecated-macro -fgnuc-version=4.2.1 -ffp-contract=on -fno-experimental-relative-c++-abi-vtables -O3 -fdebug-compilation-dir=/usr/STLcheck/sekitest/Barony/build -fcoverage-compilation-dir=/usr/STLcheck/sekitest/Barony/build -faddrsig -debugger-tuning=gdb -funwind-tables=2 -mconstructor-aliases -vectorize-loops -vectorize-slp -clear-ast-before-backend -main-file-name GameUI.cpp -finline-functions -fdiagnostics-hotness-threshold=0 -fdiagnostics-misexpect-tolerance=0 -setup-static-analyzer -D BASE_DATA_DIR="./" -D NDEBUG -D __GCC_HAVE_DWARF2_CFI_ASM=1
1// GameUI.cpp
2
3#include "GameUI.hpp"
4#include "MainMenu.hpp"
5#include "Frame.hpp"
6#include "Image.hpp"
7#include "Field.hpp"
8#include "Button.hpp"
9#include "Text.hpp"
10#include "Slider.hpp"
11#include "Widget.hpp"
12
13#include "../main.hpp"
14#include "../game.hpp"
15#include "../menu.hpp"
16#include "../interface/interface.hpp"
17#include "../interface/consolecommand.hpp"
18#include "../stat.hpp"
19#include "../player.hpp"
20#include "../draw.hpp"
21#include "../items.hpp"
22#include "../mod_tools.hpp"
23#include "../input.hpp"
24#include "../collision.hpp"
25#include "../monster.hpp"
26#include "../classdescriptions.hpp"
27#include "../shops.hpp"
28#include "../colors.hpp"
29#include "../book.hpp"
30
31#include <assert.h>
32
33const Uint32 playerColor(int index, bool colorblind, bool ally)
34{
35 Uint32 result;
36 if (colorblind) {
37 switch (index) {
38 default: result = uint32ColorPlayerX_colorblind; break;
39 case 0: result = uint32ColorPlayer1_colorblind; break;
40 case 1: result = uint32ColorPlayer2_colorblind; break;
41 case 2: result = uint32ColorPlayer3_colorblind; break;
42 case 3: result = uint32ColorPlayer4_colorblind; break;
43 case 4: result = uint32ColorPlayer5_colorblind; break;
44 case 5: result = uint32ColorPlayer6_colorblind; break;
45 case 6: result = uint32ColorPlayer7_colorblind; break;
46 case 7: result = uint32ColorPlayer8_colorblind; break;
47 }
48 }
49 else {
50 switch (index) {
51 default: result = uint32ColorPlayerX; break;
52 case 0: result = uint32ColorPlayer1; break;
53 case 1: result = uint32ColorPlayer2; break;
54 case 2: result = uint32ColorPlayer3; break;
55 case 3: result = uint32ColorPlayer4; break;
56 case 4: result = uint32ColorPlayer5; break;
57 case 5: result = uint32ColorPlayer6; break;
58 case 6: result = uint32ColorPlayer7; break;
59 case 7: result = uint32ColorPlayer8; break;
60 }
61 }
62 if (ally) {
63 uint8_t r, g, b, a;
64 getColor(result, &r, &g, &b, &a);
65 r /= 2; g /= 2; b /= 2;
66 return makeColor(r, g, b, a);
67 } else {
68 return result;
69 }
70}
71
72static const char* bigfont_outline = "fonts/pixelmix.ttf#16#2";
73static const char* bigfont_no_outline = "fonts/pixelmix.ttf#16#0";
74static const char* smallfont_outline = "fonts/pixel_maz_multiline.ttf#16#2";
75static const char* smallfont_no_outline = "fonts/pixel_maz_multiline.ttf#16#0";
76
77static ConsoleVariable<Vector4> mapBgColor("/map_background_color", Vector4{22.f, 24.f, 29.f, 223.f});
78static ConsoleVariable<Vector4> logBgColor("/log_background_color", Vector4{22.f, 24.f, 29.f, 223.f});
79static ConsoleVariable<bool> cvar_hotbar_compact_disable("/hotbar_compact_disable", false); // dont use compact at all
80static ConsoleVariable<bool> cvar_hotbar_compact_use_fullsize("/hotbar_compact_fullsize", true); // dont use extra compact view, just normal compact
81
82Frame* gameUIFrame[MAXPLAYERS4] = { nullptr };
83bool newui = true;
84int selectedCursorOpacity = 255;
85int oldSelectedCursorOpacity = 255;
86int hotbarSlotOpacity = 255;
87int hotbarSelectedSlotOpacity = 255;
88int hotbarCompactOffsetX = 0;
89real_t hotbarCompactSlotOverlapPercent = 0.0; // % of a slot to overlap facemenu buttons in compact view
90int hotbarCompactInactiveSlotMovementX = 0; // inactive facemenu groups move over by this much out of the way in compact view
91int hotbarCompactExpandedOffsetX = 0; // navigating the hotbar in inventory mode will expand the centers of groups out by this much
92int hotbarOffsetY = 0;
93int hotbarCompactOffsetY = 0;
94
95int xpbarOffsetWidth = 0;
96int xpbarOffsetY = 0;
97int xpbarCompactOffsetWidth = 0;
98int xpbarCompactOffsetY = 0;
99
100int hpmpbarOffsetWidth = 0;
101int hpmpbarOffsetX = 0;
102int hpmpbarOffsetY = 0;
103int hpmpbarCompactOffsetWidth = 0;
104int hpmpbarCompactOffsetX = 0;
105int hpmpbarCompactOffsetY = 0;
106
107int hpmpbarMaxWidthAmount = 160; // hp / mp value that makes the bar 100% size
108int hpmpbarIntervalToIncreaseWidth = 5; // hp / mp grows every x max hp/mp value
109int hpmpbarIntervalStartValue = 20; // hp / mp of this value is the smallest interval
110int hpmpbarBasePercentSize = 30; // smallest width at low hp/mp value
111real_t hpmpbarWidthIncreasePercentOnInterval = 2.5; // % to grow bar per interval
112int hpmpbarCompactMaxWidthAmount = 160; // hp / mp value that makes the bar 100% size
113int hpmpbarCompactIntervalToIncreaseWidth = 5; // hp / mp grows every x max hp/mp value
114int hpmpbarCompactIntervalStartValue = 20; // hp / mp of this value is the smallest interval
115int hpmpbarCompactBasePercentSize = 44; // smallest width at low hp/mp value
116real_t hpmpbarCompactWidthIncreasePercentOnInterval = 2.0; // % to grow bar per interval
117
118struct AllyStatusBarSettings_t
119{
120 struct HPBar_t
121 {
122 int barPixelWidth = 120; // pixel width of bar
123 int barCompactPixelWidth = 120; // pixel width of bar
124 int barSplitscreenPixelWidthOffset = 0; // amount to shrink bars in compact width mode
125 int barMaxWidthAmount = 160; // hp / mp value that makes the bar 100% size
126 int barIntervalToIncreaseWidth = 5; // hp / mp grows every x max hp/mp value
127 int barIntervalStartValue = 20; // hp / mp of this value is the smallest interval
128 int barBasePercentSize = 30; // smallest width at low hp/mp value
129 real_t barWidthIncreasePercentOnInterval = 2.5; // % to grow bar per interval
130 int barCompactMaxWidthAmount = 160; // hp / mp value that makes the bar 100% size
131 int barCompactIntervalToIncreaseWidth = 5; // hp / mp grows every x max hp/mp value
132 int barCompactIntervalStartValue = 20; // hp / mp of this value is the smallest interval
133 int barCompactBasePercentSize = 44; // smallest width at low hp/mp value
134 real_t barCompactWidthIncreasePercentOnInterval = 2.0; // % to grow bar per interval
135 };
136 struct MPBar_t
137 {
138 int barPixelWidth = 120; // pixel width of bar
139 int barCompactPixelWidth = 120; // pixel width of bar
140 int barMaxWidthAmount = 160; // hp / mp value that makes the bar 100% size
141 int barIntervalToIncreaseWidth = 5; // hp / mp grows every x max hp/mp value
142 int barIntervalStartValue = 20; // hp / mp of this value is the smallest interval
143 int barBasePercentSize = 30; // smallest width at low hp/mp value
144 real_t barWidthIncreasePercentOnInterval = 2.5; // % to grow bar per interval
145 int barCompactMaxWidthAmount = 160; // hp / mp value that makes the bar 100% size
146 int barCompactIntervalToIncreaseWidth = 5; // hp / mp grows every x max hp/mp value
147 int barCompactIntervalStartValue = 20; // hp / mp of this value is the smallest interval
148 int barCompactBasePercentSize = 44; // smallest width at low hp/mp value
149 real_t barCompactWidthIncreasePercentOnInterval = 2.0; // % to grow bar per interval
150 };
151 struct EntrySettings_t
152 {
153 int entryHeight = 40;
154 int entryCompactHeight = 40;
155 int maxNameLengthFullsize = 10;
156 int maxNameLengthCompact = 10;
157 int maxNameLengthSplitscreenFullsize = 10;
158 int maxNameLengthSplitscreenCompact = 10;
159 int baseY = 20;
160 int baseYSplitscreen = 20;
161 };
162 struct FollowerBars_t
163 {
164 static EntrySettings_t entrySettings;
165 static HPBar_t hpBar;
166 static MPBar_t mpBar;
167 };
168 struct PlayerBars_t
169 {
170 static EntrySettings_t entrySettings;
171 static HPBar_t hpBar;
172 static MPBar_t mpBar;
173 };
174};
175AllyStatusBarSettings_t::EntrySettings_t AllyStatusBarSettings_t::FollowerBars_t::entrySettings;
176AllyStatusBarSettings_t::EntrySettings_t AllyStatusBarSettings_t::PlayerBars_t::entrySettings;
177AllyStatusBarSettings_t::HPBar_t AllyStatusBarSettings_t::FollowerBars_t::hpBar;
178AllyStatusBarSettings_t::MPBar_t AllyStatusBarSettings_t::FollowerBars_t::mpBar;
179AllyStatusBarSettings_t::HPBar_t AllyStatusBarSettings_t::PlayerBars_t::hpBar;
180AllyStatusBarSettings_t::MPBar_t AllyStatusBarSettings_t::PlayerBars_t::mpBar;
181
182struct MessageZoneSettings_t
183{
184 enum HotbarTypes_t : int {
185 HOTBAR_CLASSIC,
186 HOTBAR_MODERN_GAMEPAD,
187 HOTBAR_MODERN_KEYBOARD
188 };
189 struct MessageSettings_t
190 {
191 HotbarTypes_t hotbarType = HOTBAR_CLASSIC;
192 Player::MessageZone_t::ChatAlignment_t alignment = Player::MessageZone_t::ALIGN_LEFT_BOTTOM;
193 enum LayoutType_t : int {
194 LAYOUT_DEFAULT,
195 LAYOUT_COMPACT
196 };
197 struct Layout_t
198 {
199 LayoutType_t layoutType = LAYOUT_DEFAULT;
200 int offsetY = 0;
201 int maxMessages = 10;
202 };
203 std::map<LayoutType_t, Layout_t> layouts;
204 };
205 std::map<HotbarTypes_t, std::map<Player::MessageZone_t::ChatAlignment_t, MessageSettings_t>> settings;
206 MessageSettings_t& addSetting(const HotbarTypes_t _hotbarType, const Player::MessageZone_t::ChatAlignment_t _alignment)
207 {
208 settings[_hotbarType][_alignment] = MessageSettings_t();
209 auto& setting = settings[_hotbarType][_alignment];
210 setting.hotbarType = _hotbarType;
211 setting.alignment = _alignment;
212 return setting;
213 }
214 MessageSettings_t::Layout_t& getLayout(const int player, const Player::MessageZone_t::ChatAlignment_t _alignment)
215 {
216 bool compact = players[player]->bUseCompactGUIHeight();
217 auto layout = compact ? MessageSettings_t::LayoutType_t::LAYOUT_COMPACT : MessageSettings_t::LayoutType_t::LAYOUT_DEFAULT;
218 if ( players[player]->hotbar.useHotbarFaceMenu )
219 {
220 if ( inputs.hasController(player) )
221 {
222 return settings[HOTBAR_MODERN_GAMEPAD][_alignment].layouts[layout];
223 }
224 else
225 {
226 return settings[HOTBAR_MODERN_KEYBOARD][_alignment].layouts[layout];
227 }
228 }
229 else
230 {
231 return settings[HOTBAR_CLASSIC][_alignment].layouts[layout];
232 }
233 }
234};
235MessageZoneSettings_t messageZoneSettings;
236
237struct DamageIndicatorSettings_t
238{
239 enum LayoutType_t : int {
240 LAYOUT_DEFAULT,
241 LAYOUT_2P_WIDE,
242 LAYOUT_2P_TALL,
243 LAYOUT_4P
244 };
245 struct Layout_t
246 {
247 int image_size = 3;
248 int radius_x = 200;
249 int radius_y = 200;
250 };
251 std::map<LayoutType_t, Layout_t> settings;
252 real_t fadeSpeed = 1.0;
253 Uint32 fadeAfterTicks = HITRATE45;
254 Uint32 deleteAfterTicks = 45;
255 std::string indicatorDamageFramePaths[4] = {
256 "images/ui/HUD/indicators/damageB",
257 "images/ui/HUD/indicators/damage",
258 "images/ui/HUD/indicators/damage",
259 "images/ui/HUD/indicators/damage"
260 };
261 std::string indicatorBlockedFramePaths[4] = {
262 "images/ui/HUD/indicators/damage_blocked",
263 "images/ui/HUD/indicators/damage_blocked",
264 "images/ui/HUD/indicators/damage_blocked",
265 "images/ui/HUD/indicators/damage_blocked"
266 };
267};
268DamageIndicatorSettings_t damageIndicatorSettings;
269
270std::vector<int> LevelUpAnimBreakpoints;
271LevelUpAnimation_t levelUpAnimation[MAXPLAYERS4];
272
273std::map<Player::WorldUI_t::WorldTooltipDialogue_t::DialogueType_t,
274 Player::WorldUI_t::WorldTooltipDialogue_t::WorldDialogueSettings_t::Setting_t> Player::WorldUI_t::WorldTooltipDialogue_t::WorldDialogueSettings_t::settings;
275real_t Player::WorldUI_t::WorldTooltipItem_t::WorldItemSettings_t::scaleMod = 0.0;
276real_t Player::WorldUI_t::WorldTooltipItem_t::WorldItemSettings_t::opacity = 0.0;
277
278bool bUsePreciseFieldTextReflow = true;
279bool bUseSelectedSlotCycleAnimation = false; // probably not gonna use, but can enable
280CustomColors_t hudColors;
281EnemyBarSettings_t enemyBarSettings;
282#ifdef BARONY_SUPER_MULTIPLAYER
283StatusEffectQueue_t StatusEffectQueue[MAXPLAYERS4] = { {0}, {1}, {2}, {3}, {4}, {5}, {6}, {7} };
284#else
285StatusEffectQueue_t StatusEffectQueue[MAXPLAYERS4] = { {0}, {1}, {2}, {3} };
286#endif
287std::unordered_map<int, StatusEffectQueue_t::EffectDefinitionEntry_t> StatusEffectQueue_t::StatusEffectDefinitions_t::allEffects;
288std::unordered_map<int, StatusEffectQueue_t::EffectDefinitionEntry_t> StatusEffectQueue_t::StatusEffectDefinitions_t::allSustainedSpells;
289Uint32 StatusEffectQueue_t::StatusEffectDefinitions_t::tooltipDescColor = 0xFFFFFFFF;
290Uint32 StatusEffectQueue_t::StatusEffectDefinitions_t::tooltipHeadingColor = 0xFFFFFFFF;
291Uint32 StatusEffectQueue_t::StatusEffectDefinitions_t::notificationTextColor = 0xFFFFFFFF;
292std::string StatusEffectQueue_t::StatusEffectDefinitions_t::notificationFont = "fonts/pixelmix.ttf#16#2";
293
294std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& tag, std::string& rawValue);
295
296int GAMEUI_FRAMEDATA_ANIMATING_ITEM = 1;
297int GAMEUI_FRAMEDATA_ALCHEMY_ITEM = 2;
298int GAMEUI_FRAMEDATA_ALCHEMY_RECIPE_SLOT = 3;
299int GAMEUI_FRAMEDATA_ALCHEMY_RECIPE_ENTRY = 4;
300int GAMEUI_FRAMEDATA_WORLDTOOLTIP_ITEM = 5;
301int GAMEUI_FRAMEDATA_SHOP_ITEM = 6;
302
303MinotaurWarning_t minotaurWarning[MAXPLAYERS4];
304
305void updateLevelUpFrame(const int player);
306void updateSkillUpFrame(const int player);
307void drawClockwiseSquareMesh(const char* texture, float lerp, SDL_Rect rect, Uint32 color);
308
309void capitalizeString(std::string& str)
310{
311 if ( str.size() < 1 ) { return; }
312 char letter = str[0];
313 if ( letter >= 'a' && letter <= 'z' )
314 {
315 str[0] = toupper(letter);
316 }
317}
318
319void uppercaseString(std::string& str)
320{
321 if ( str.size() < 1 ) { return; }
322 for ( auto& letter : str )
323 {
324 if ( letter >= 'a' && letter <= 'z' )
325 {
326 letter = toupper(letter);
327 }
328 }
329}
330
331void camelCaseString(std::string& str)
332{
333 if ( str.size() < 1 ) { return; }
334 char prevLetter = ' ';
335 for ( auto& letter : str )
336 {
337 if ( letter >= 'a' && letter <= 'z' && prevLetter == ' ' )
338 {
339 letter = toupper(letter);
340 }
341 prevLetter = letter;
342 }
343}
344
345bool stringStartsWithVowel(std::string& str)
346{
347 if ( str.size() < 1 ) { return false; }
348 switch ( str[0] )
349 {
350 case 'a':
351 case 'e':
352 case 'i':
353 case 'o':
354 case 'u':
355 case 'A':
356 case 'E':
357 case 'I':
358 case 'O':
359 case 'U':
360 return true;
361 default:
362 break;
363 }
364 return false;
365}
366
367std::string EnemyBarSettings_t::getEnemyBarSpriteName(Entity* entity)
368{
369 if ( !entity ) { return "default"; }
370
371 if ( entity->behavior == &actPlayer || entity->behavior == &actMonster )
372 {
373 int type = entity->getMonsterTypeFromSprite();
374 if ( type < NUMMONSTERS && type >= 0 )
375 {
376 if ( type == MIMIC )
377 {
378 if ( entity->isInertMimic() )
379 {
380 return "chest_mimic";
381 }
382 }
383 return monstertypename[type];
384 }
385 }
386 else if ( entity->behavior == &actDoor )
387 {
388 return "door";
389 }
390 else if ( entity->behavior == &actChest )
391 {
392 return "chest";
393 }
394 else if ( entity->isDamageableCollider() )
395 {
396 auto& colliderData = EditorEntityData_t::colliderData[entity->colliderDamageTypes];
397 return colliderData.hpbarLookupName;
398 }
399 else if ( entity->behavior == &actFurniture )
400 {
401 switch ( entity->furnitureType )
402 {
403 case FURNITURE_TABLE:
404 case FURNITURE_PODIUM:
405 return "table";
406 break;
407 default:
408 return "chair";
409 break;
410 }
411 }
412 return "default";
413}
414
415enum ImageIndexes9x9 : int
416{
417 TOP_LEFT,
418 TOP_RIGHT,
419 TOP,
420 MIDDLE_LEFT,
421 MIDDLE_RIGHT,
422 MIDDLE,
423 BOTTOM_LEFT,
424 BOTTOM_RIGHT,
425 BOTTOM
426};
427
428const std::vector<std::string> skillsheetEffectBackgroundImages =
429{
430 "9x9 bg top left",
431 "9x9 bg top right",
432 "9x9 bg top middle",
433 "9x9 bg left",
434 "9x9 bg right",
435 "9x9 bg middle",
436 "9x9 bg bottom left",
437 "9x9 bg bottom right",
438 "9x9 bg bottom middle"
439};
440
441void imageSetWidthHeight9x9(Frame* container, const std::vector<std::string>& imgNames)
442{
443 for ( auto& img : imgNames )
444 {
445 if ( auto i = container->findImage(img.c_str()) )
446 {
447 if ( auto imgGet = Image::get(i->path.c_str()) )
448 {
449 i->pos.w = (int)imgGet->getWidth();
450 i->pos.h = (int)imgGet->getHeight();
451 }
452 }
453 }
454}
455
456// for 9x9 images stretched to fit a container
457void imageResizeToContainer9x9(Frame* container, SDL_Rect dimensionsToFill, const std::vector<std::string>& imgNames)
458{
459 assert(imgNames.size() == 9)(static_cast<void> (0));
460 // adjust inner background image elements
461 auto tl = container->findImage(imgNames[TOP_LEFT].c_str());
462 tl->pos.x = dimensionsToFill.x;
463 tl->pos.y = dimensionsToFill.y;
464 auto tr = container->findImage(imgNames[TOP_RIGHT].c_str());
465 tr->pos.x = dimensionsToFill.w - tr->pos.w;
466 tr->pos.y = dimensionsToFill.y;
467 auto tm = container->findImage(imgNames[TOP].c_str());
468 tm->pos.x = tl->pos.x + tl->pos.w;
469 tm->pos.y = dimensionsToFill.y;
470 tm->pos.w = dimensionsToFill.w - tr->pos.w - tl->pos.w;
471 auto bl = container->findImage(imgNames[BOTTOM_LEFT].c_str());
472 bl->pos.x = dimensionsToFill.x;
473 bl->pos.y = dimensionsToFill.h - bl->pos.h;
474 auto br = container->findImage(imgNames[BOTTOM_RIGHT].c_str());
475 br->pos.x = dimensionsToFill.w - br->pos.w;
476 br->pos.y = bl->pos.y;
477 auto bm = container->findImage(imgNames[BOTTOM].c_str());
478 bm->pos.x = bl->pos.x + bl->pos.w;
479 bm->pos.w = dimensionsToFill.w - bl->pos.w - br->pos.w;
480 bm->pos.y = bl->pos.y;
481 auto ml = container->findImage(imgNames[MIDDLE_LEFT].c_str());
482 ml->pos.x = dimensionsToFill.x;
483 ml->pos.y = tl->pos.y + tl->pos.h;
484 ml->pos.h = dimensionsToFill.h - dimensionsToFill.y - bl->pos.h - tl->pos.h;
485 auto mr = container->findImage(imgNames[MIDDLE_RIGHT].c_str());
486 mr->pos.x = dimensionsToFill.w - mr->pos.w;
487 mr->pos.y = ml->pos.y;
488 mr->pos.h = ml->pos.h;
489 auto mm = container->findImage(imgNames[MIDDLE].c_str());
490 mm->pos.x = ml->pos.x + ml->pos.w;
491 mm->pos.y = ml->pos.y;
492 mm->pos.w = dimensionsToFill.w - ml->pos.w - mr->pos.w;
493 mm->pos.h = ml->pos.h;
494}
495
496static ConsoleVariable<bool> cvar_mp_flash_whole_bar("/mp_flash_whole_bar", true);
497struct MPBarPaths_t
498{
499 static const std::map<std::string, std::string> normalMPBars;
500 static const std::map<std::string, std::string> automatonHTBars;
501 static const std::map<std::string, std::string> automatonSTBars;
502 static const std::map<std::string, std::string> insectoidENBars;
503 static const std::map<std::string, std::string> warningMPBars;
504 static const std::map<std::string, std::string>& getMPBar(const int player)
505 {
506 /*if ( keystatus[SDLK_h] )
507 {
508 return automatonHTBars;
509 }*/
510 if ( stats[player]->type == AUTOMATON )
511 {
512 /*if ( keystatus[SDLK_g] )
513 {
514 return automatonHTBars;
515 }*/
516 return automatonSTBars;
517 }
518 else if ( stats[player]->playerRace == RACE_INSECTOID && stats[player]->appearance == 0 )
519 {
520 return insectoidENBars;
521 }
522 return normalMPBars;
523 }
524 static const std::string& get(const int player, const std::string key)
525 {
526 if ( (players[player]->magic.noManaProcessedOnTick != 0
527 && players[player]->magic.noManaFeedbackTicks % 20 < 10) )
528 {
529 if ( *cvar_mp_flash_whole_bar )
530 {
531 if ( key.find("fade") == std::string::npos )
532 {
533 return warningMPBars.at(key);
534 }
535 }
536 else
537 {
538 if ( key == "mp img value" )
539 {
540 return warningMPBars.at(key);
541 }
542 }
543 }
544 return getMPBar(player).at(key);
545 }
546};
547
548const std::map<std::string, std::string> MPBarPaths_t::normalMPBars = {
549 { "mp img progress bot", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPBot_00.png" },
550 { "mp img progress bot 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPBot_01.png" },
551 { "mp img progress bot 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPBot_02.png" },
552 { "mp img progress bot 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPBot_03.png" },
553
554 { "mp img progress endcap","*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_00.png" },
555 { "mp img progress endcap 1","*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_01.png" },
556
557 { "mp img progress endcap flash", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_F00.png" },
558 { "mp img progress endcap flash 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_F01.png" },
559 { "mp img progress endcap flash 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_F02.png" },
560 { "mp img progress endcap flash 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_F03.png" },
561 { "mp img progress endcap flash 4", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_F04.png" },
562 { "mp img progress endcap flash b", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_F00b.png" },
563 { "mp img progress endcap flash c", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_F00c.png" },
564 { "mp img progress endcap flash d", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_F00d.png" },
565
566 { "mp img fade endcap", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEndFade_00.png" },
567
568 { "mp img progress", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPMid_00.png" },
569 { "mp img progress 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPMid_01.png" },
570 { "mp img progress 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPMid_02.png" },
571 { "mp img progress 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPMid_03.png" },
572
573 { "mp img fade bot", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPMidFade_00.png" },
574 { "mp img fade", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPMidFade_00.png" },
575
576 { "mp img value", "*#images/ui/HUD/hpmpbars/HUD_Bars_MPNumBase_00.png" }
577};
578
579const std::map<std::string, std::string> MPBarPaths_t::automatonHTBars = {
580 { "mp img progress bot", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTBot_00.png" },
581 { "mp img progress bot 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTBot_01.png" },
582 { "mp img progress bot 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTBot_02.png" },
583 { "mp img progress bot 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTBot_03.png" },
584
585 { "mp img progress endcap","*#images/ui/HUD/hpmpbars/HUD_Bars_HTEnd_00.png" },
586 { "mp img progress endcap 1","*#images/ui/HUD/hpmpbars/HUD_Bars_HTEnd_01.png" },
587
588 { "mp img progress endcap flash", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTEnd_F00.png" },
589 { "mp img progress endcap flash 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTEnd_F01.png" },
590 { "mp img progress endcap flash 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTEnd_F02.png" },
591 { "mp img progress endcap flash 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTEnd_F03.png" },
592 { "mp img progress endcap flash 4", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTEnd_F04.png" },
593 { "mp img progress endcap flash b", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTEnd_F00b.png" },
594 { "mp img progress endcap flash c", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTEnd_F00c.png" },
595 { "mp img progress endcap flash d", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTEnd_F00d.png" },
596
597 { "mp img fade endcap", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTEndFade_00.png" },
598
599 { "mp img progress", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTMid_00.png" },
600 { "mp img progress 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTMid_01.png" },
601 { "mp img progress 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTMid_02.png" },
602 { "mp img progress 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTMid_03.png" },
603
604 { "mp img fade bot", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTMidFade_00.png" },
605 { "mp img fade", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTMidFade_00.png" },
606
607 { "mp img value", "*#images/ui/HUD/hpmpbars/HUD_Bars_HTNumBase_00.png" }
608};
609
610const std::map<std::string, std::string> MPBarPaths_t::automatonSTBars = {
611 { "mp img progress bot", "*#images/ui/HUD/hpmpbars/HUD_Bars_STBot_00.png" },
612 { "mp img progress bot 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_STBot_01.png" },
613 { "mp img progress bot 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_STBot_02.png" },
614 { "mp img progress bot 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_STBot_03.png" },
615
616 { "mp img progress endcap","*#images/ui/HUD/hpmpbars/HUD_Bars_STEnd_00.png" },
617 { "mp img progress endcap 1","*#images/ui/HUD/hpmpbars/HUD_Bars_STEnd_01.png" },
618
619 { "mp img progress endcap flash", "*#images/ui/HUD/hpmpbars/HUD_Bars_STEnd_F00.png" },
620 { "mp img progress endcap flash 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_STEnd_F01.png" },
621 { "mp img progress endcap flash 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_STEnd_F02.png" },
622 { "mp img progress endcap flash 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_STEnd_F03.png" },
623 { "mp img progress endcap flash 4", "*#images/ui/HUD/hpmpbars/HUD_Bars_STEnd_F04.png" },
624 { "mp img progress endcap flash b", "*#images/ui/HUD/hpmpbars/HUD_Bars_STEnd_F00b.png" },
625 { "mp img progress endcap flash c", "*#images/ui/HUD/hpmpbars/HUD_Bars_STEnd_F00c.png" },
626 { "mp img progress endcap flash d", "*#images/ui/HUD/hpmpbars/HUD_Bars_STEnd_F00d.png" },
627
628 { "mp img fade endcap", "*#images/ui/HUD/hpmpbars/HUD_Bars_STEndFade_00.png" },
629
630 { "mp img progress", "*#images/ui/HUD/hpmpbars/HUD_Bars_STMid_00.png" },
631 { "mp img progress 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_STMid_01.png" },
632 { "mp img progress 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_STMid_02.png" },
633 { "mp img progress 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_STMid_03.png" },
634
635 { "mp img fade bot", "*#images/ui/HUD/hpmpbars/HUD_Bars_STMidFade_00.png" },
636 { "mp img fade", "*#images/ui/HUD/hpmpbars/HUD_Bars_STMidFade_00.png" },
637
638 { "mp img value", "*#images/ui/HUD/hpmpbars/HUD_Bars_STNumBase_00.png" }
639};
640
641const std::map<std::string, std::string> MPBarPaths_t::insectoidENBars = {
642 { "mp img progress bot", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENBot_00.png" },
643 { "mp img progress bot 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENBot_01.png" },
644 { "mp img progress bot 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENBot_02.png" },
645 { "mp img progress bot 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENBot_03.png" },
646
647 { "mp img progress endcap","*#images/ui/HUD/hpmpbars/HUD_Bars_ENEnd_00.png" },
648 { "mp img progress endcap 1","*#images/ui/HUD/hpmpbars/HUD_Bars_ENEnd_01.png" },
649
650 { "mp img progress endcap flash", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENEnd_F00.png" },
651 { "mp img progress endcap flash 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENEnd_F01.png" },
652 { "mp img progress endcap flash 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENEnd_F02.png" },
653 { "mp img progress endcap flash 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENEnd_F03.png" },
654 { "mp img progress endcap flash 4", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENEnd_F04.png" },
655 { "mp img progress endcap flash b", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENEnd_F00b.png" },
656 { "mp img progress endcap flash c", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENEnd_F00c.png" },
657 { "mp img progress endcap flash d", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENEnd_F00d.png" },
658
659 { "mp img fade endcap", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENEndFade_00.png" },
660
661 { "mp img progress", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENMid_00.png" },
662 { "mp img progress 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENMid_01.png" },
663 { "mp img progress 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENMid_02.png" },
664 { "mp img progress 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENMid_03.png" },
665
666 { "mp img fade bot", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENMidFade_00.png" },
667 { "mp img fade", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENMidFade_00.png" },
668
669 { "mp img value", "*#images/ui/HUD/hpmpbars/HUD_Bars_ENNumBase_00.png" }
670};
671
672const std::map<std::string, std::string> MPBarPaths_t::warningMPBars = {
673 { "mp img progress bot", "*#images/ui/HUD/hpmpbars/HUD_Bars_NABot_00.png" },
674 { "mp img progress bot 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_NABot_01.png" },
675 { "mp img progress bot 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_NABot_02.png" },
676 { "mp img progress bot 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_NABot_03.png" },
677
678 { "mp img progress endcap","*#images/ui/HUD/hpmpbars/HUD_Bars_NAEnd_00.png" },
679 { "mp img progress endcap 1","*#images/ui/HUD/hpmpbars/HUD_Bars_NAEnd_01.png" },
680
681 { "mp img progress endcap flash", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAEnd_F00.png" },
682 { "mp img progress endcap flash 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAEnd_F01.png" },
683 { "mp img progress endcap flash 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAEnd_F02.png" },
684 { "mp img progress endcap flash 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAEnd_F03.png" },
685 { "mp img progress endcap flash 4", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAEnd_F04.png" },
686 { "mp img progress endcap flash b", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAEnd_F00b.png" },
687 { "mp img progress endcap flash c", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAEnd_F00c.png" },
688 { "mp img progress endcap flash d", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAEnd_F00d.png" },
689
690 { "mp img fade endcap", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAEndFade_00.png" },
691
692 { "mp img progress", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAMid_00.png" },
693 { "mp img progress 1", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAMid_01.png" },
694 { "mp img progress 2", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAMid_02.png" },
695 { "mp img progress 3", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAMid_03.png" },
696
697 { "mp img fade bot", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAMidFade_00.png" },
698 { "mp img fade", "*#images/ui/HUD/hpmpbars/HUD_Bars_NAMidFade_00.png" },
699
700 { "mp img value", "*#images/ui/HUD/hpmpbars/HUD_Bars_NANumBase_00.png" }
701};
702
703void createHPMPBars(const int player)
704{
705 auto& hud_t = players[player]->hud;
706 const int barTotalHeight = hud_t.HPMP_FRAME_HEIGHT;
707 const int hpBarStartY = (hud_t.hudFrame->getSize().h - hud_t.HPMP_FRAME_START_Y);
708 const int mpBarStartY = hpBarStartY + barTotalHeight;
709 const int barWidth = hud_t.HPMP_FRAME_WIDTH;
710 const int barStartX = hud_t.HPMP_FRAME_START_X;
711 {
712 hud_t.hpFrame = hud_t.hudFrame->addFrame("hp bar");
713 hud_t.hpFrame->setHollow(true);
714
715 SDL_Rect pos{ barStartX, hpBarStartY, barWidth, barTotalHeight };
716 hud_t.hpFrame->setSize(pos);
717
718 auto fadeFrame = hud_t.hpFrame->addFrame("hp fade frame");
719 fadeFrame->setSize(SDL_Rect{0, 0, barWidth, barTotalHeight});
720 fadeFrame->setInheritParentFrameOpacity(false);
721
722 auto foregroundFrame = hud_t.hpFrame->addFrame("hp foreground frame");
723 foregroundFrame->setSize(SDL_Rect{ 0, 0, barWidth, barTotalHeight });
724
725
726 auto base = hud_t.hpFrame->addImage(SDL_Rect{ 54, 4, barWidth - 54, 26 }, 0xFFFFFFFF,
727 "*#images/ui/HUD/hpmpbars/HUD_Bars_Base_00.png", "hp img base");
728
729 const int progressBarHeight = 22;
730 auto fadeProgressBase = fadeFrame->addImage(SDL_Rect{ 54, 6, 6, progressBarHeight }, 0xFFFFFFFF,
731 "*#images/ui/HUD/hpmpbars/HUD_Bars_HPMidFade_00.png", "hp img fade bot");
732 auto fadeProgress = fadeFrame->addImage(SDL_Rect{ 60, 6, barWidth - 60 - 8, progressBarHeight }, 0xFFFFFFFF,
733 "*#images/ui/HUD/hpmpbars/HUD_Bars_HPMidFade_00.png", "hp img fade");
734 auto fadeProgressEndCap = fadeFrame->addImage(SDL_Rect{
735 fadeProgress->pos.x + fadeProgress->pos.w, 6, 8, progressBarHeight }, 0xFFFFFFFF,
736 "*#images/ui/HUD/hpmpbars/HUD_Bars_HPEndFade_00.png", "hp img fade endcap");
737
738 auto numbase = foregroundFrame->addImage(SDL_Rect{ 0, 4, 48, 26 }, 0xFFFFFFFF,
739 "*#images/ui/HUD/hpmpbars/HUD_Bars_HPNumBase_00.png", "hp img value");
740 auto div = foregroundFrame->addImage(SDL_Rect{ 46, 0, 8, 34 }, 0xFFFFFFFF,
741 "*#images/ui/HUD/hpmpbars/HUD_Bars_Separator_00.png", "hp img div");
742
743 auto currentProgressBase = foregroundFrame->addImage(SDL_Rect{ 54, 6, 6, progressBarHeight }, 0xFFFFFFFF,
744 "*#images/ui/HUD/hpmpbars/HUD_Bars_HPBot_00.png", "hp img progress bot");
745 auto currentProgress = foregroundFrame->addImage(SDL_Rect{ 60, 6, barWidth - 60 - 8, progressBarHeight }, 0xFFFFFFFF,
746 "*#images/ui/HUD/hpmpbars/HUD_Bars_HPMid_00.png", "hp img progress");
747 auto currentProgressEndCap = foregroundFrame->addImage(SDL_Rect{
748 currentProgress->pos.x + currentProgress->pos.w, 6, 8, progressBarHeight }, 0xFFFFFFFF,
749 "*#images/ui/HUD/hpmpbars/HUD_Bars_HPEnd_00.png", "hp img progress endcap");
750 auto currentProgressEndCapFlash = foregroundFrame->addImage(SDL_Rect{
751 currentProgress->pos.x + currentProgress->pos.w - 14, 6, 22, progressBarHeight }, 0xFFFFFFFF,
752 "*#images/ui/HUD/hpmpbars/HUD_Bars_HPEnd_F00.png", "hp img progress endcap flash");
753 currentProgressEndCapFlash->disabled = true;
754
755 const int endCapWidth = 16;
756 auto endCap = foregroundFrame->addImage(SDL_Rect{ pos.w - endCapWidth, 0, endCapWidth, barTotalHeight }, 0xFFFFFFFF,
757 "*#images/ui/HUD/hpmpbars/HUD_Bars_EndCap_00.png", "hp img endcap");
758
759 auto div25Percent = foregroundFrame->addImage(SDL_Rect{ 0, 8, 2, 18 }, 0xFFFFFFFF,
760 "*#images/ui/HUD/hpmpbars/HUD_Bars_Divider_01.png", "hp img div 25pc");
761 div25Percent->disabled = true;
762 auto div50Percent = foregroundFrame->addImage(SDL_Rect{ 0, 8, 2, 18 }, 0xFFFFFFFF,
763 "*#images/ui/HUD/hpmpbars/HUD_Bars_Divider_01.png", "hp img div 50pc");
764 div50Percent->disabled = true;
765 auto div75Percent = foregroundFrame->addImage(SDL_Rect{ 0, 8, 2, 18 }, 0xFFFFFFFF,
766 "*#images/ui/HUD/hpmpbars/HUD_Bars_Divider_01.png", "hp img div 75pc");
767 div75Percent->disabled = true;
768
769 auto font = "fonts/pixel_maz.ttf#32#2";
770 auto hptext = foregroundFrame->addField("hp text", 16);
771 hptext->setText("0");
772 hptext->setSize(numbase->pos);
773 hptext->setFont(font);
774 hptext->setVJustify(Field::justify_t::CENTER);
775 hptext->setHJustify(Field::justify_t::CENTER);
776 hptext->setColor(makeColor( 255, 255, 255, 255));
777 }
778
779 // MP bar below
780 {
781 hud_t.mpFrame = hud_t.hudFrame->addFrame("mp bar");
782 hud_t.mpFrame->setHollow(true);
783
784 SDL_Rect pos{ barStartX, mpBarStartY, barWidth, barTotalHeight };
785 hud_t.mpFrame->setSize(pos);
786
787 auto fadeFrame = hud_t.mpFrame->addFrame("mp fade frame");
788 fadeFrame->setSize(SDL_Rect{ 0, 0, barWidth, barTotalHeight });
789 fadeFrame->setInheritParentFrameOpacity(false);
790
791 auto foregroundFrame = hud_t.mpFrame->addFrame("mp foreground frame");
792 foregroundFrame->setSize(SDL_Rect{ 0, 0, barWidth, barTotalHeight });
793
794
795 auto base = hud_t.mpFrame->addImage(SDL_Rect{ 54, 4, barWidth - 54, 26 }, 0xFFFFFFFF,
796 "*#images/ui/HUD/hpmpbars/HUD_Bars_Base_00.png", "mp img base");
797
798 const int progressBarHeight = 22;
799 auto fadeProgressBase = fadeFrame->addImage(SDL_Rect{ 54, 6, 6, progressBarHeight }, 0xFFFFFFFF,
800 "*#images/ui/HUD/hpmpbars/HUD_Bars_MPMidFade_00.png", "mp img fade bot");
801 auto fadeProgress = fadeFrame->addImage(SDL_Rect{ 60, 6, barWidth - 60 - 8, progressBarHeight }, 0xFFFFFFFF,
802 "*#images/ui/HUD/hpmpbars/HUD_Bars_MPMidFade_00.png", "mp img fade");
803 auto fadeProgressEndCap = fadeFrame->addImage(SDL_Rect{
804 fadeProgress->pos.x + fadeProgress->pos.w, 6, 8, progressBarHeight }, 0xFFFFFFFF,
805 "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEndFade_00.png", "mp img fade endcap");
806
807 auto numbase = foregroundFrame->addImage(SDL_Rect{ 0, 4, 48, 26 }, 0xFFFFFFFF,
808 "*#images/ui/HUD/hpmpbars/HUD_Bars_MPNumBase_00.png", "mp img value");
809 auto div = foregroundFrame->addImage(SDL_Rect{ 46, 0, 8, 34 }, 0xFFFFFFFF,
810 "*#images/ui/HUD/hpmpbars/HUD_Bars_Separator_00.png", "mp img div");
811
812 auto currentProgressBase = foregroundFrame->addImage(SDL_Rect{ 54, 6, 6, progressBarHeight }, 0xFFFFFFFF,
813 "*#images/ui/HUD/hpmpbars/HUD_Bars_MPBot_00.png", "mp img progress bot");
814 auto currentProgress = foregroundFrame->addImage(SDL_Rect{ 60, 6, barWidth - 60 - 8, progressBarHeight }, 0xFFFFFFFF,
815 "*#images/ui/HUD/hpmpbars/HUD_Bars_MPMid_00.png", "mp img progress");
816 auto currentProgressEndCap = foregroundFrame->addImage(SDL_Rect{
817 currentProgress->pos.x + currentProgress->pos.w, 6, 8, progressBarHeight }, 0xFFFFFFFF,
818 "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_00.png", "mp img progress endcap");
819 auto currentProgressEndCapFlash = foregroundFrame->addImage(SDL_Rect{
820 currentProgress->pos.x + currentProgress->pos.w - 14, 6, 22, progressBarHeight }, 0xFFFFFFFF,
821 "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_F00.png", "mp img progress endcap flash");
822 currentProgressEndCapFlash->disabled = true;
823
824 const int endCapWidth = 16;
825 auto endCap = foregroundFrame->addImage(SDL_Rect{ pos.w - endCapWidth, 0, endCapWidth, barTotalHeight }, 0xFFFFFFFF,
826 "*#images/ui/HUD/hpmpbars/HUD_Bars_EndCap_00.png", "mp img endcap");
827
828 auto div25Percent = foregroundFrame->addImage(SDL_Rect{ 0, 8, 2, 18 }, 0xFFFFFFFF,
829 "*#images/ui/HUD/hpmpbars/HUD_Bars_Divider_01.png", "mp img div 25pc");
830 div25Percent->disabled = true;
831 auto div50Percent = foregroundFrame->addImage(SDL_Rect{ 0, 8, 2, 18 }, 0xFFFFFFFF,
832 "*#images/ui/HUD/hpmpbars/HUD_Bars_Divider_01.png", "mp img div 50pc");
833 div50Percent->disabled = true;
834 auto div75Percent = foregroundFrame->addImage(SDL_Rect{ 0, 8, 2, 18 }, 0xFFFFFFFF,
835 "*#images/ui/HUD/hpmpbars/HUD_Bars_Divider_01.png", "mp img div 75pc");
836 div75Percent->disabled = true;
837
838 auto font = "fonts/pixel_maz.ttf#32#2";
839 auto mptext = foregroundFrame->addField("mp text", 16);
840 mptext->setText("0");
841 mptext->setSize(numbase->pos);
842 mptext->setFont(font);
843 mptext->setVJustify(Field::justify_t::CENTER);
844 mptext->setHJustify(Field::justify_t::CENTER);
845 mptext->setColor(makeColor( 255, 255, 255, 255));
846 }
847}
848
849void createAllyFollowerFrame(const int player)
850{
851 auto& hud_t = players[player]->hud;
852
853 auto frame = hud_t.hudFrame->addFrame("follower status");
854 hud_t.allyFollowerFrame = frame;
855 frame->setHollow(true);
856 frame->setSize(SDL_Rect{ 0, 0, 300, 0 });
857 frame->setDisabled(true);
858 frame->setScrollBarsEnabled(false);
859 frame->setAllowScrollBinds(false);
860
861 auto selector = frame->addImage(SDL_Rect{ 0, 0, 8, 8 }, 0xFFFFFFFF, "*#images/ui/HUD/allies/HUD_Ally_Arrow_00.png", "selector");
862 selector->disabled = true;
863 selector->ontop = true;
864
865 auto glyphFrame = hud_t.hudFrame->addFrame("follower glyphs");
866 hud_t.allyFollowerGlyphFrame = glyphFrame;
867 glyphFrame->setHollow(true);
868 glyphFrame->setSize(SDL_Rect{ 0, 0, 300, 0 });
869 glyphFrame->setDisabled(true);
870 {
871 std::string font = "fonts/pixel_maz.ttf#32#2";
872 /*auto commandText = glyphFrame->addField("command prompt txt", 32);
873 commandText->setFont(font.c_str());
874 commandText->setHJustify(Field::justify_t::LEFT);
875 commandText->setVJustify(Field::justify_t::TOP);
876 commandText->setSize(SDL_Rect{ 0, 0, glyphFrame->getSize().w, 24 });
877 commandText->setText(Language::get(4202));
878 commandText->setColor(hudColors.characterSheetLightNeutral);
879 commandText->setOntop(true);
880 commandText->setDisabled(true);*/
881
882 auto glyphCommand = glyphFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "glyph command");
883 glyphCommand->disabled = true;
884 glyphCommand->ontop = true;
885
886 auto imgCommand = glyphFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
887 "*#images/ui/HUD/allies/HUD_Ally_Circle_00.png", "img command");
888 imgCommand->disabled = true;
889 imgCommand->ontop = true;
890
891 /*auto repeatText = glyphFrame->addField("repeat prompt txt", 32);
892 repeatText->setFont(font.c_str());
893 repeatText->setHJustify(Field::justify_t::LEFT);
894 repeatText->setVJustify(Field::justify_t::TOP);
895 repeatText->setSize(SDL_Rect{ 0, 0, frame->getSize().w, 24 });
896 repeatText->setText(Language::get(4203));
897 repeatText->setColor(hudColors.characterSheetLightNeutral);
898 repeatText->setOntop(true);
899 repeatText->setDisabled(true);*/
900
901 auto glyphRepeat = glyphFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "glyph repeat");
902 glyphRepeat->disabled = true;
903 glyphRepeat->ontop = true;
904
905 auto imgRepeat = glyphFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
906 "*#images/ui/HUD/allies/HUD_Ally_Repeat_00.png", "img repeat");
907 imgRepeat->disabled = true;
908 imgRepeat->ontop = true;
909 }
910}
911
912void createCalloutPromptFrame(const int player)
913{
914 auto& hud_t = players[player]->hud;
915
916 auto frame = hud_t.hudFrame->addFrame("callout prompts");
917 hud_t.calloutPromptFrame = frame;
918 frame->setHollow(true);
919 frame->setSize(SDL_Rect{ 0, 0, 300, 0 });
920 frame->setDisabled(true);
921 frame->setScrollBarsEnabled(false);
922 frame->setAllowScrollBinds(false);
923
924 auto glyph = frame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "glyph");
925 glyph->disabled = true;
926 auto icon = frame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "icon");
927 icon->disabled = true;
928
929 auto text = frame->addField("prompt", 64);
930 text->setFont(smallfont_outline);
931 text->setHJustify(Field::justify_t::LEFT);
932 text->setVJustify(Field::justify_t::TOP);
933 text->setSize(SDL_Rect{ 0, 0, frame->getSize().w, 24 });
934 text->setText(Language::get(6049));
935 text->setColor(0xFFFFFFFF);
936 text->setOntop(true);
937 text->setDisabled(true);
938}
939
940const int kPlayerBarsEntryFrameWidth = 214;
941
942void updateCalloutPromptFrame(const int player)
943{
944 auto& hud_t = players[player]->hud;
945 auto frame = hud_t.calloutPromptFrame;
946 if ( !frame )
947 {
948 return;
949 }
950
951 if ( !CalloutRadialMenu::calloutMenuEnabledForGamemode() )
952 {
953 frame->setDisabled(true);
954 return;
955 }
956 if ( !Player::getPlayerInteractEntity(player) )
957 {
958 frame->setDisabled(true);
959 return;
960 }
961 if ( splitscreen && player == 3 )
962 {
963 frame->setDisabled(true); // we overlap the minimap, so don't draw this one
964 return;
965 }
966 else if ( !players[player]->shootmode && !FollowerMenu[player].followerMenuIsOpen()
967 && !CalloutMenu[player].calloutMenuIsOpen() )
968 {
969 frame->setDisabled(true);
970 return;
971 }
972
973 int alignX = 8;
974 int alignY = 0;
975 static ConsoleVariable<bool> cvar_callout_prompt_horizontal("/callout_prompt_horizontal", false);
976 if ( hud_t.allyPlayerFrame && !hud_t.allyPlayerFrame->isDisabled()
977 && hud_t.allyPlayerFrame->getOpacity() > 0.0 )
978 {
979 if ( hud_t.playerBars.size() > 0 )
980 {
981 if ( *cvar_callout_prompt_horizontal )
982 {
983 alignX += kPlayerBarsEntryFrameWidth;
984 }
985 else
986 {
987 alignY += hud_t.playerBars.size() * 36;
988 }
989 }
990 }
991
992 frame->setDisabled(false);
993 SDL_Rect framePos;
994 framePos.x = alignX;
995 framePos.y = players[player]->bUseCompactGUIWidth() ? AllyStatusBarSettings_t::FollowerBars_t::entrySettings.baseYSplitscreen : AllyStatusBarSettings_t::FollowerBars_t::entrySettings.baseY;
996 framePos.y -= 4;
997 framePos.y += alignY;
998 framePos.w = 300;
999 framePos.h = 50;
1000 frame->setSize(framePos);
1001
1002 auto glyph = frame->findImage("glyph");
1003 auto glyphPathUnpressed = Input::inputs[player].getGlyphPathForBinding("Call Out", false);
1004 auto glyphPathPressed = Input::inputs[player].getGlyphPathForBinding("Call Out", true);
1005 glyph->disabled = true;
1006
1007 const int nominalGlyphHeight = 26;
1008 int unpressedHeight = 0;
1009 int unpressedY = 0;
1010 if ( ticks % 50 < 25 && (CalloutMenu[player].calloutMenuIsOpen() && CalloutMenu[player].selectMoveTo) )
1011 {
1012 glyph->path = glyphPathPressed;
1013 if ( auto imgGet = Image::get(glyph->path.c_str()) )
1014 {
1015 glyph->disabled = false;
1016 SDL_Rect glyphPos{ 0, 8, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
1017 glyph->pos = glyphPos;
1018 if ( auto imgGetUnpressed = Image::get(glyphPathUnpressed.c_str()) )
1019 {
1020 unpressedHeight = imgGetUnpressed->getHeight();
1021 unpressedY = glyph->pos.y;
1022 if ( unpressedHeight != glyph->pos.h )
1023 {
1024 glyph->pos.y -= (glyph->pos.h - unpressedHeight);
1025 }
1026
1027 if ( unpressedHeight != nominalGlyphHeight )
1028 {
1029 unpressedY -= (unpressedHeight - nominalGlyphHeight) / 2;
1030 glyph->pos.y -= (unpressedHeight - nominalGlyphHeight) / 2;
1031 }
1032 }
1033 }
1034 }
1035 else
1036 {
1037 glyph->path = glyphPathUnpressed;
1038 if ( auto imgGet = Image::get(glyph->path.c_str()) )
1039 {
1040 glyph->disabled = false;
1041 SDL_Rect glyphPos{ 0, 8, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
1042 glyph->pos = glyphPos;
1043 unpressedHeight = glyph->pos.h;
1044 unpressedY = glyph->pos.y;
1045 if ( glyph->pos.h != nominalGlyphHeight )
1046 {
1047 unpressedY -= (glyph->pos.h - nominalGlyphHeight) / 2;
1048 glyph->pos.y -= (glyph->pos.h - nominalGlyphHeight) / 2;
1049 }
1050 }
1051 }
1052
1053 int glyphAlignY = unpressedY + (unpressedHeight - nominalGlyphHeight) / 2;
1054
1055 auto icon = frame->findImage("icon");
1056 icon->disabled = true;
1057 static ConsoleVariable<bool> cvar_callout_prompt_wheel("/callout_prompt_wheel", false);
1058 if ( !glyph->disabled )
1059 {
1060 if ( *cvar_callout_prompt_wheel &&
1061 CalloutMenu[player].calloutMenuIsOpen() && CalloutMenu[player].selectMoveTo )
1062 {
1063 icon->path = "*images/ui/HUD/HUD_Ally_Callout_Wheel_00.png";
1064 }
1065 else
1066 {
1067 icon->path = "*images/ui/HUD/HUD_Ally_Callout_00.png";
1068 }
1069 if ( auto imgGet = Image::get(icon->path.c_str()) )
1070 {
1071 icon->disabled = false;
1072 icon->pos.w = imgGet->getWidth();
1073 icon->pos.h = imgGet->getHeight();
1074 icon->pos.x = glyph->pos.x + glyph->pos.w + 4;
1075 icon->pos.y = unpressedY + (unpressedHeight) / 2 - icon->pos.h / 2;
1076 if ( icon->pos.y % 2 == 1 )
1077 {
1078 ++icon->pos.y;
1079 }
1080 }
1081 }
1082
1083 auto text = frame->findField("prompt");
1084 text->setDisabled(true);
1085 static ConsoleVariable<bool> cvar_callout_prompt_text("/callout_prompt_text", false);
1086 if ( !icon->disabled && *cvar_callout_prompt_text )
1087 {
1088 if ( CalloutMenu[player].calloutMenuIsOpen() && CalloutMenu[player].selectMoveTo )
1089 {
1090 text->setDisabled(false);
1091 text->setText(Language::get(6049));
1092 SDL_Rect pos = text->getSize();
1093 pos.x = icon->pos.x + icon->pos.w + 4;
1094 pos.y = glyphAlignY - 10;
1095 text->setSize(pos);
1096 }
1097 }
1098}
1099
1100void createAllyFollowerTitleFrame(const int player)
1101{
1102 auto& hud_t = players[player]->hud;
1103
1104 auto frame = hud_t.hudFrame->addFrame("follower title status");
1105 hud_t.allyFollowerTitleFrame = frame;
1106 frame->setHollow(true);
1107 frame->setSize(SDL_Rect{ 0, 0, 300, 0 });
1108 frame->setDisabled(true);
1109
1110 auto selector = frame->addImage(SDL_Rect{ 0, 0, 8, 8 }, 0xFFFFFFFF, "*#images/ui/HUD/allies/HUD_Ally_Arrow_00.png", "selector");
1111 selector->disabled = true;
1112 selector->ontop = true;
1113
1114 auto glyphSelector = frame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "glyph selector");
1115 glyphSelector->disabled = true;
1116 glyphSelector->ontop = true;
1117}
1118
1119void createAllyPlayerFrame(const int player)
1120{
1121 auto& hud_t = players[player]->hud;
1122
1123 auto frame = hud_t.hudFrame->addFrame("player status");
1124 hud_t.allyPlayerFrame = frame;
1125 frame->setHollow(true);
1126 frame->setSize(SDL_Rect{ 0, 0, 300, 0 });
1127 frame->setDisabled(true);
1128 frame->setScrollBarsEnabled(false);
1129 frame->setAllowScrollBinds(false);
1130}
1131
1132Frame* createAllyPlayerEntry(const int player)
1133{
1134 auto& hud_t = players[player]->hud;
1135 Frame* baseFrame = hud_t.allyPlayerFrame;
1136
1137 auto entry = baseFrame->addFrame("entry");
1138 const int allyPlayerEntryHeight = 40;
1139 entry->setSize(SDL_Rect{ 0, 0, kPlayerBarsEntryFrameWidth, allyPlayerEntryHeight });
1140 entry->setHollow(true);
1141 entry->setInheritParentFrameOpacity(false);
1142
1143 Frame* portrait = nullptr;
1144 {
1145 portrait = entry->addFrame("portrait");
1146 portrait->setSize(SDL_Rect{ 0, 0, 36, 36 });
1147 portrait->addImage(SDL_Rect{ 0, 0, 32, 32 }, 0xFFFFFFFF, "*#images/ui/HUD/allies/HUD_HPBar_HeadDefaultM_00.png", "portrait img");
1148 }
1149
1150 std::string font = "fonts/pixel_maz.ttf#32#2";
1151 {
1152 Field* name = entry->addField("name", 128);
1153 name->setFont(font.c_str());
1154 name->setHJustify(Field::justify_t::LEFT);
1155 name->setVJustify(Field::justify_t::TOP);
1156 name->setSize(SDL_Rect{ portrait->getSize().x + portrait->getSize().w - 4, 0, entry->getSize().w, 24 });
1157 name->setText("");
1158 name->setColor(hudColors.characterSheetLightNeutral);
1159 name->setOntop(true);
1160 }
1161 {
1162 Field* level = entry->addField("level", 32);
1163 level->setFont("fonts/pixel_maz.ttf#32#2");
1164 level->setHJustify(Field::justify_t::RIGHT);
1165 level->setVJustify(Field::justify_t::TOP);
1166 level->setSize(SDL_Rect{ 0, 14, entry->getSize().w - 16, 24 });
1167 level->setText("");
1168 level->setColor(hudColors.characterSheetLightNeutral);
1169 level->setOntop(true);
1170 }
1171 {
1172 Field* hp = entry->addField("hp", 32);
1173 hp->setFont(font.c_str());
1174 hp->setHJustify(Field::justify_t::RIGHT);
1175 hp->setVJustify(Field::justify_t::TOP);
1176 hp->setSize(SDL_Rect{ 0, 0, entry->getSize().w - 16, 24 });
1177 hp->setText("");
1178 hp->setColor(makeColor(255, 255, 255, 255));
1179 hp->setOntop(true);
1180 }
1181
1182 const int hpHeight = 16;
1183 {
1184 Frame* hpFrame = entry->addFrame("hp");
1185 hpFrame->setSize(SDL_Rect{ portrait->getSize().x + portrait->getSize().w - 4, 16, entry->getSize().w - (portrait->getSize().x + portrait->getSize().w - 4), hpHeight });
1186 SDL_Rect hpFramePos = hpFrame->getSize();
1187 hpFrame->setHollow(true);
1188
1189 auto mid = hpFrame->addImage(SDL_Rect{ 6, 2, 40, 12 }, 0xFFFFFFFF,
1190 "*#images/ui/HUD/allies/HUD_HPBar_Mid_00.png", "hp img mid");
1191 auto endCap = hpFrame->addImage(SDL_Rect{ mid->pos.x + mid->pos.w, 0, 6, 16 }, 0xFFFFFFFF,
1192 "*#images/ui/HUD/allies/HUD_HPBar_End_00.png", "hp img endcap");
1193
1194 auto progressBase = hpFrame->addImage(SDL_Rect{ 6, 4, 4, 8 }, 0xFFFFFFFF,
1195 "*#images/ui/HUD/allies/HUD_HPBar_Fill_Base_00.png", "hp img progress base");
1196 auto progressMid = hpFrame->addImage(SDL_Rect{ 6, 4, 2, 8 }, 0xFFFFFFFF,
1197 "*#images/ui/HUD/allies/HUD_HPBar_Fill_Mid_00.png", "hp img progress");
1198 auto progressEndCap = hpFrame->addImage(SDL_Rect{ 0, 4, 4, 8 }, 0xFFFFFFFF,
1199 "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_00.png", "hp img progress endcap");
1200
1201 auto progressEndcapDamaged = hpFrame->addImage(SDL_Rect{ 6, 4, 18, 8 }, 0xFFFFFFFF,
1202 "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_Damaged.png", "hp img progress damaged");
1203 progressEndcapDamaged->disabled = true;
1204
1205 auto progressEndCapFlash = hpFrame->addImage(SDL_Rect{
1206 0, 4, 18, 8 }, 0xFFFFFFFF,
1207 "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_00.png", "hp img progress endcap flash");
1208 progressEndCapFlash->disabled = true;
1209
1210 auto base = hpFrame->addImage(SDL_Rect{ 0, 0, 6, 16 }, 0xFFFFFFFF,
1211 "*#images/ui/HUD/allies/HUD_HPBar_Base_00.png", "hp img base");
1212
1213 }
1214
1215 const int mpHeight = 6;
1216 {
1217 Frame* mpFrame = entry->addFrame("mp");
1218 mpFrame->setSize(SDL_Rect{ portrait->getSize().x + portrait->getSize().w, 30, entry->getSize().w - (portrait->getSize().x + portrait->getSize().w - 4), mpHeight });
1219 SDL_Rect mpFramePos = mpFrame->getSize();
1220 mpFrame->setHollow(true);
1221
1222 auto mid = mpFrame->addImage(SDL_Rect{ 2, 0, 40, 6 }, 0xFFFFFFFF,
1223 "*#images/ui/HUD/allies/HUD_MPBar_Mid_00.png", "mp img mid");
1224 auto endCap = mpFrame->addImage(SDL_Rect{ mid->pos.x + mid->pos.w, 0, 4, 6 }, 0xFFFFFFFF,
1225 "*#images/ui/HUD/allies/HUD_MPBar_End_00.png", "mp img endcap");
1226
1227 auto progressBase = mpFrame->addImage(SDL_Rect{ 2, 0, 4, 4 }, 0xFFFFFFFF,
1228 "*#images/ui/HUD/allies/HUD_MPBar_Fill_Base_00.png", "mp img progress base");
1229 auto progressMid = mpFrame->addImage(SDL_Rect{ 2, 0, 4, 4 }, 0xFFFFFFFF,
1230 "*#images/ui/HUD/allies/HUD_MPBar_Fill_Mid_00.png", "mp img progress");
1231 auto progressEndCap = mpFrame->addImage(SDL_Rect{ 0, 0, 4, 4 }, 0xFFFFFFFF,
1232 "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_00.png", "mp img progress endcap");
1233
1234 auto progressEndcapDamaged = mpFrame->addImage(SDL_Rect{ 0, 0, 8, 4 }, 0xFFFFFFFF,
1235 "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_Damaged.png", "mp img progress damaged");
1236 progressEndcapDamaged->disabled = true;
1237
1238 auto progressEndCapFlash = mpFrame->addImage(SDL_Rect{
1239 0, 0, 8, 4 }, 0xFFFFFFFF,
1240 "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_00.png", "mp img progress endcap flash");
1241 progressEndCapFlash->disabled = true;
1242
1243 auto base = mpFrame->addImage(SDL_Rect{ 0, 0, 4, 6 }, 0xFFFFFFFF,
1244 "*#images/ui/HUD/allies/HUD_MPBar_Base_00.png", "mp img base");
1245 }
1246
1247 entry->setDrawCallback([](const Widget& widget, SDL_Rect rect) {
1248 if ( widget.getUserData() && PingNetworkStatus_t::bEnabled )
1249 {
1250 int player = reinterpret_cast<intptr_t>(widget.getUserData()) - 1;
1251 if ( player < 0 || player >= MAXPLAYERS4 ) {
1252 return;
1253 }
1254
1255 auto value = PingNetworkStatus[player].displayMillisImmediate;
1256 const int divideInterval = 25;
1257 Image* img = nullptr;
1258 Image* warningImg = nullptr;
1259 if ( value > 0 )
1260 {
1261 char buf[32];
1262 if ( value < PingNetworkStatus_t::pingLimitGreen )
1263 {
1264 if ( PingNetworkStatus[player].hudDisplayOKTicks == 0 )
1265 {
1266 PingNetworkStatus[player].hudDisplayOKTicks = ticks;
1267 }
1268 if ( PingNetworkStatus_t::pingHUDDisplayGreen ||
1269 (PingNetworkStatus_t::pingHUDShowOKBriefly
1270 && ((ticks - PingNetworkStatus[player].hudDisplayOKTicks) < 5 * TICKS_PER_SECOND50)) )
1271 {
1272 img = Image::get("#*images/ui/HUD/Ping_Green.png");
1273 }
1274 }
1275 else if ( value < PingNetworkStatus_t::pingLimitYellow )
1276 {
1277 if ( PingNetworkStatus[player].hudDisplayOKTicks == 0 )
1278 {
1279 PingNetworkStatus[player].hudDisplayOKTicks = ticks;
1280 }
1281 if ( PingNetworkStatus_t::pingHUDDisplayYellow ||
1282 (PingNetworkStatus_t::pingHUDShowOKBriefly
1283 && ((ticks - PingNetworkStatus[player].hudDisplayOKTicks) < 5 * TICKS_PER_SECOND50)) )
1284 {
1285 img = Image::get("#*images/ui/HUD/Ping_Yellow.png");
1286 }
1287 }
1288 else if ( value < PingNetworkStatus_t::pingLimitOrange )
1289 {
1290 PingNetworkStatus[player].hudDisplayOKTicks = 0;
1291 if ( PingNetworkStatus_t::pingHUDDisplayOrange )
1292 {
1293 img = Image::get("#*images/ui/HUD/Ping_Orange.png");
1294 warningImg = Image::get("#*images/ui/HUD/warning_glyph.png");
1295 }
1296 }
1297 else
1298 {
1299 PingNetworkStatus[player].hudDisplayOKTicks = 0;
1300 if ( PingNetworkStatus_t::pingHUDDisplayRed )
1301 {
1302 img = Image::get("#*images/ui/HUD/Ping_Red.png");
1303 warningImg = Image::get("#*images/ui/HUD/danger_glyph.png");
1304 }
1305 }
1306 }
1307 else
1308 {
1309 img = nullptr;
1310 PingNetworkStatus[player].hudDisplayOKTicks = 0;
1311 }
1312 if ( img )
1313 {
1314 const auto frame = static_cast<const Frame*>(&widget);
1315 SDL_Rect pos = frame->getSize();
1316 pos.x = pos.x + pos.w - 4;
1317 pos.y = pos.y + 8;
1318 pos.w = img->getWidth();
1319 pos.h = img->getHeight();
1320 img->drawColor(nullptr, pos, SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY },
1321 makeColor(255, 255, 255, 255 * (frame->getOpacity() / 100.0)));
1322
1323 if ( warningImg )
1324 {
1325 pos.y += 14;
1326 warningImg->drawColor(nullptr, pos, SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY },
1327 makeColor(255, 255, 255, 255 * (frame->getOpacity() / 100.0)));
1328 }
1329
1330 if ( PingNetworkStatus_t::pingHUDShowNumericValue )
1331 {
1332 pos.x += 20;
1333 pos.y = frame->getSize().y + 9;
1334 char buf[32];
1335 snprintf(buf, sizeof(buf), "%dMS", PingNetworkStatus[player].displayMillis);
1336 if ( auto textGet = Text::get(buf, smallfont_outline,
1337 makeColor(134, 159, 165, 255), makeColor(0, 0, 0, 255)) )
1338 {
1339 textGet->drawColor(SDL_Rect{ 0,0,0,0 }, SDL_Rect{ pos.x, pos.y, 0, 0 },
1340 SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY },
1341 makeColor(255, 255, 255, 255 * (frame->getOpacity() / 100.0)));
1342 }
1343 }
1344 }
1345 }
1346 });
1347 return entry;
1348}
1349
1350Frame* createAllyFollowerEntry(const int player, Frame* baseFrame)
1351{
1352 auto& hud_t = players[player]->hud;
1353
1354 auto entry = baseFrame->addFrame("entry");
1355 const int allyFollowerEntryHeight = 40;
1356 entry->setSize(SDL_Rect{ 0, 0, 300, allyFollowerEntryHeight });
1357 entry->setHollow(true);
1358 entry->setInheritParentFrameOpacity(false);
1359
1360 if ( baseFrame == players[player]->hud.allyFollowerTitleFrame )
1361 {
1362 auto bgImg = entry->addImage(SDL_Rect{ 0, 0, 0, 0 }, makeColor(255, 255, 255, 255), "*images/ui/HUD/allies/HUD_Ally_TitleBG_Left_00.png", "bg img left");
1363 bgImg->disabled = true;
1364 if ( auto imgGet = Image::get(bgImg->path.c_str()) )
1365 {
1366 bgImg->pos.w = imgGet->getWidth();
1367 bgImg->pos.h = imgGet->getHeight();
1368 }
1369
1370 bgImg = entry->addImage(SDL_Rect{ 0, 0, 0, 0 }, makeColor(255, 255, 255, 255), "*images/ui/HUD/allies/HUD_Ally_TitleBG_Mid_00.png", "bg img mid");
1371 bgImg->disabled = true;
1372 if ( auto imgGet = Image::get(bgImg->path.c_str()) )
1373 {
1374 bgImg->pos.w = imgGet->getWidth();
1375 bgImg->pos.h = imgGet->getHeight();
1376 }
1377
1378 bgImg = entry->addImage(SDL_Rect{ 0, 0, 0, 0 }, makeColor(255, 255, 255, 255), "*images/ui/HUD/allies/HUD_Ally_TitleBG_Right_00.png", "bg img right");
1379 bgImg->disabled = true;
1380 if ( auto imgGet = Image::get(bgImg->path.c_str()) )
1381 {
1382 bgImg->pos.w = imgGet->getWidth();
1383 bgImg->pos.h = imgGet->getHeight();
1384 }
1385 }
1386
1387 Frame* portrait = nullptr;
1388 {
1389 portrait = entry->addFrame("portrait");
1390 portrait->setSize(SDL_Rect{ 0, 0, 36, 36 });
1391 portrait->addImage(SDL_Rect{ 0, 0, 32, 32 }, 0xFFFFFFFF, "*#images/ui/HUD/allies/HUD_HPBar_HeadDefaultM_00.png", "portrait img");
1392 }
1393
1394 std::string font = "fonts/pixel_maz.ttf#32#2";
1395 {
1396 Field* name = entry->addField("name", 128);
1397 name->setFont(font.c_str());
1398 name->setHJustify(Field::justify_t::LEFT);
1399 name->setVJustify(Field::justify_t::TOP);
1400 name->setSize(SDL_Rect{ portrait->getSize().x + portrait->getSize().w - 4, 0, entry->getSize().w, 24 });
1401 name->setText("");
1402 name->setColor(hudColors.characterSheetLightNeutral);
1403 name->setOntop(true);
1404 }
1405 {
1406 Field* level = entry->addField("level", 32);
1407 level->setFont("fonts/pixel_maz.ttf#32#2");
1408 level->setHJustify(Field::justify_t::RIGHT);
1409 level->setVJustify(Field::justify_t::TOP);
1410 level->setSize(SDL_Rect{ 0, 14, entry->getSize().w - 16, 24 });
1411 level->setText("");
1412 level->setColor(hudColors.characterSheetLightNeutral);
1413 level->setOntop(true);
1414 }
1415 {
1416 Field* hp = entry->addField("hp", 32);
1417 hp->setFont(font.c_str());
1418 hp->setHJustify(Field::justify_t::RIGHT);
1419 hp->setVJustify(Field::justify_t::TOP);
1420 hp->setSize(SDL_Rect{ 0, 0, entry->getSize().w - 16, 24 });
1421 hp->setText("");
1422 hp->setColor(makeColor(255, 255, 255, 255));
1423 hp->setOntop(true);
1424 }
1425
1426 const int hpHeight = 16;
1427 {
1428 Frame* hpFrame = entry->addFrame("hp");
1429 hpFrame->setSize(SDL_Rect{ portrait->getSize().x + portrait->getSize().w - 4, 16, entry->getSize().w - (portrait->getSize().x + portrait->getSize().w - 4), hpHeight });
1430 SDL_Rect hpFramePos = hpFrame->getSize();
1431 hpFrame->setHollow(true);
1432
1433 auto mid = hpFrame->addImage(SDL_Rect{ 6, 2, 40, 12 }, 0xFFFFFFFF,
1434 "*#images/ui/HUD/allies/HUD_HPBar_Mid_00.png", "hp img mid");
1435 auto endCap = hpFrame->addImage(SDL_Rect{ mid->pos.x + mid->pos.w, 0, 6, 16 }, 0xFFFFFFFF,
1436 "*#images/ui/HUD/allies/HUD_HPBar_End_00.png", "hp img endcap");
1437
1438 auto progressBase = hpFrame->addImage(SDL_Rect{ 6, 4, 4, 8 }, 0xFFFFFFFF,
1439 "*#images/ui/HUD/allies/HUD_HPBar_Fill_Base_00.png", "hp img progress base");
1440 auto progressMid = hpFrame->addImage(SDL_Rect{ 6, 4, 2, 8 }, 0xFFFFFFFF,
1441 "*#images/ui/HUD/allies/HUD_HPBar_Fill_Mid_00.png", "hp img progress");
1442 auto progressEndCap = hpFrame->addImage(SDL_Rect{ 0, 4, 4, 8 }, 0xFFFFFFFF,
1443 "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_00.png", "hp img progress endcap");
1444
1445 auto progressEndcapDamaged = hpFrame->addImage(SDL_Rect{ 6, 4, 18, 8 }, 0xFFFFFFFF,
1446 "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_Damaged.png", "hp img progress damaged");
1447 progressEndcapDamaged->disabled = true;
1448
1449 auto progressEndCapFlash = hpFrame->addImage(SDL_Rect{
1450 0, 4, 18, 8 }, 0xFFFFFFFF,
1451 "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_00.png", "hp img progress endcap flash");
1452 progressEndCapFlash->disabled = true;
1453
1454 auto base = hpFrame->addImage(SDL_Rect{ 0, 0, 6, 16 }, 0xFFFFFFFF,
1455 "*#images/ui/HUD/allies/HUD_HPBar_Base_00.png", "hp img base");
1456
1457 }
1458
1459 const int mpHeight = 6;
1460 if ( false )
1461 {
1462 Frame* mpFrame = entry->addFrame("mp");
1463 mpFrame->setSize(SDL_Rect{ portrait->getSize().x + portrait->getSize().w, 30, entry->getSize().w - (portrait->getSize().x + portrait->getSize().w - 4), mpHeight });
1464 SDL_Rect mpFramePos = mpFrame->getSize();
1465 mpFrame->setHollow(true);
1466
1467 auto mid = mpFrame->addImage(SDL_Rect{ 2, 0, 40, 6 }, 0xFFFFFFFF,
1468 "*#images/ui/HUD/allies/HUD_MPBar_Mid_00.png", "mp img mid");
1469 auto endCap = mpFrame->addImage(SDL_Rect{ mid->pos.x + mid->pos.w, 0, 4, 6 }, 0xFFFFFFFF,
1470 "*#images/ui/HUD/allies/HUD_MPBar_End_00.png", "mp img endcap");
1471
1472 auto progressBase = mpFrame->addImage(SDL_Rect{ 2, 0, 4, 4 }, 0xFFFFFFFF,
1473 "*#images/ui/HUD/allies/HUD_MPBar_Fill_Base_00.png", "mp img progress base");
1474 auto progressMid = mpFrame->addImage(SDL_Rect{ 2, 0, 4, 4 }, 0xFFFFFFFF,
1475 "*#images/ui/HUD/allies/HUD_MPBar_Fill_Mid_00.png", "mp img progress");
1476 auto progressEndCap = mpFrame->addImage(SDL_Rect{ 0, 0, 4, 4 }, 0xFFFFFFFF,
1477 "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_00.png", "mp img progress endcap");
1478
1479 auto progressEndcapDamaged = mpFrame->addImage(SDL_Rect{ 0, 0, 8, 4 }, 0xFFFFFFFF,
1480 "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_Damaged.png", "mp img progress damaged");
1481 progressEndcapDamaged->disabled = true;
1482
1483 auto progressEndCapFlash = mpFrame->addImage(SDL_Rect{
1484 0, 0, 8, 4 }, 0xFFFFFFFF,
1485 "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_00.png", "mp img progress endcap flash");
1486 progressEndCapFlash->disabled = true;
1487
1488 auto base = mpFrame->addImage(SDL_Rect{ 0, 0, 4, 6 }, 0xFFFFFFFF,
1489 "*#images/ui/HUD/allies/HUD_MPBar_Base_00.png", "mp img base");
1490 }
1491 return entry;
1492}
1493
1494bool Player::HUD_t::FollowerDisplay_t::infiniteScrolling = true;
1495int Player::HUD_t::FollowerDisplay_t::numFiniteBars = 8;;
1496int Player::HUD_t::FollowerDisplay_t::numInfiniteFullsizeBars = 8;
1497int Player::HUD_t::FollowerDisplay_t::numInfiniteCompactBars = 16;
1498int Player::HUD_t::FollowerDisplay_t::numInfiniteSplitscreenFullsizeBars = 4;
1499int Player::HUD_t::FollowerDisplay_t::numInfiniteSplitscreenCompactBars = 8;
1500int Player::HUD_t::FollowerDisplay_t::getNumEntriesToShow(const int playernum)
1501{
1502 auto& hud_t = players[playernum]->hud;
1503 if ( !infiniteScrolling )
1504 {
1505 return numFiniteBars;
1506 }
1507 if ( !splitscreen || !players[playernum]->bUseCompactGUIHeight() )
1508 {
1509 if ( list_Size(&stats[playernum]->FOLLOWERS) < numInfiniteFullsizeBars )
1510 {
1511 return numInfiniteFullsizeBars;
1512 }
1513 else
1514 {
1515 return numInfiniteCompactBars;
1516 }
1517 }
1518 else
1519 {
1520 if ( list_Size(&stats[playernum]->FOLLOWERS) < numInfiniteSplitscreenFullsizeBars )
1521 {
1522 return numInfiniteSplitscreenFullsizeBars;
1523 }
1524 else
1525 {
1526 return numInfiniteSplitscreenCompactBars;
1527 }
1528 }
1529}
1530
1531bool Player::HUD_t::FollowerDisplay_t::getCompactMode(const int playernum)
1532{
1533 auto& hud_t = players[playernum]->hud;
1534 if ( !infiniteScrolling )
1535 {
1536 return false;
1537 }
1538 if ( !splitscreen || !players[playernum]->bUseCompactGUIHeight() )
1539 {
1540 if ( list_Size(&stats[playernum]->FOLLOWERS) < numInfiniteFullsizeBars )
1541 {
1542 return false;
1543 }
1544 else
1545 {
1546 return true;
1547 }
1548 }
1549 else
1550 {
1551 if ( list_Size(&stats[playernum]->FOLLOWERS) < numInfiniteSplitscreenFullsizeBars )
1552 {
1553 return false;
1554 }
1555 else
1556 {
1557 return true;
1558 }
1559 }
1560}
1561
1562Frame* getAllyBarTitleFrameEntry(const int player)
1563{
1564 auto& hud_t = players[player]->hud;
1565 Frame* entry = hud_t.allyFollowerTitleFrame->findFrame("entry");
1566 if ( !entry )
1567 {
1568 entry = createAllyFollowerEntry(player, hud_t.allyFollowerTitleFrame);
1569 }
1570 return entry;
1571}
1572
1573void updateAllyBarFrame(const int player, Frame* baseFrame, int activeBars, int realActiveBars, const bool bPlayerBars)
1574{
1575 Frame::image_t* selector = baseFrame->findImage("selector");
1576 if ( selector )
1577 {
1578 selector->disabled = true;
1579 }
1580 Frame::image_t* titleSelector = !bPlayerBars ? players[player]->hud.allyFollowerTitleFrame->findImage("selector") : nullptr;
1581 if ( titleSelector )
1582 {
1583 titleSelector->disabled = true;
1584 }
1585 Frame::image_t* titleSelectorGlyph = !bPlayerBars ? players[player]->hud.allyFollowerTitleFrame->findImage("glyph selector") : nullptr;
1586 if ( titleSelectorGlyph )
1587 {
1588 titleSelectorGlyph->disabled = true;
1589 }
1590
1591 std::vector<Frame*> allyFrames;
1592 int barIndex = -1;
1593 for ( auto f : baseFrame->getFrames() )
1594 {
1595 if ( strcmp(f->getName(), "entry") || f->isToBeDeleted() )
1596 {
1597 continue;
1598 }
1599 ++barIndex;
1600
1601 if ( barIndex >= activeBars )
1602 {
1603 f->removeSelf();
1604 continue;
1605 }
1606 allyFrames.push_back(f);
1607 }
1608
1609 while ( allyFrames.size() < activeBars )
1610 {
1611 allyFrames.push_back(bPlayerBars ? createAllyPlayerEntry(player) : createAllyFollowerEntry(player, baseFrame));
1612 }
1613
1614 auto& hud_t = players[player]->hud;
1615
1616 Uint32 recentFollowerUID = 0;
1617 if ( FollowerMenu[player].recentEntity )
1618 {
1619 recentFollowerUID = FollowerMenu[player].recentEntity->getUID();
1620 }
1621
1622 auto& infiniteScrolling = Player::HUD_t::FollowerDisplay_t::infiniteScrolling;
1623 /*if ( enableDebugKeys && keystatus[SDLK_F] )
1624 {
1625 keystatus[SDLK_F] = 0;
1626 infiniteScrolling = !infiniteScrolling;
1627 }*/
1628
1629 auto& followerDisplay = hud_t.followerDisplay;
1630
1631 Frame* titleFrame = nullptr;
1632 if ( infiniteScrolling && !bPlayerBars )
1633 {
1634 titleFrame = getAllyBarTitleFrameEntry(player);
1635 titleFrame->setDisabled(true);
1636 }
1637
1638 const int kNumEntriesToShow = Player::HUD_t::FollowerDisplay_t::getNumEntriesToShow(player);
1639
1640 int currentY = 0;
1641 int entryHeightForGlyphs = 0;
1642 int baseFrameHeight = 0;
1643 int selectorX = 0;
1644 bool madeSelection = false;
1645 bool updateTitleFrame = false;
1646 bool doneTitleFrame = false;
1647 barIndex = -1;
1648 bool halfWidthBars = (splitscreen && players[player]->bUseCompactGUIWidth())
1649 /*|| (enableDebugKeys && keystatus[SDLK_G])*/;
1650
1651 std::set<Uint32> followerUids;
1652 for ( node_t* node = stats[player]->FOLLOWERS.first; node != nullptr; node = node->next )
1653 {
1654 if ( (Uint32*)node->element )
1655 {
1656 followerUids.emplace(*((Uint32*)node->element));
1657 }
1658 }
1659
1660 for ( auto it = (bPlayerBars ? hud_t.playerBars.begin() : hud_t.followerBars.begin());
1661 it != (bPlayerBars ? hud_t.playerBars.end() : hud_t.followerBars.end()); )
1662 {
1663 ++barIndex;
1664 auto& followerBar = (*it).second;
1665 Frame* entryFrame = allyFrames[barIndex];
1666
1667 bool doAnimation = true;
1668 bool shortBars = players[player]->hud.followerDisplay.bCompact && infiniteScrolling && !bPlayerBars;
1669 if ( followerBar.dummy )
1670 {
1671 doAnimation = true;
1672 }
1673 else if ( followerBar.selected )
1674 {
1675 if ( updateTitleFrame )
1676 {
1677 shortBars = false;
1678 entryFrame = titleFrame;
1679 titleFrame->setDisabled(false);
1680 doneTitleFrame = true;
1681 doAnimation = false;
1682 }
1683 }
1684
1685 auto& HPBar = followerBar.hpBar;
1686 auto& MPBar = followerBar.mpBar;
1687
1688 if ( doAnimation )
1689 {
1690 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
1691 /*if ( !players[player]->shootmode && !FollowerMenu[player].followerMenuIsOpen() )
1692 {
1693 real_t setpointDiffX = fpsScale * std::max(.01, (followerBar.animx)) / 2.0;
1694 followerBar.animx -= setpointDiffX;
1695 followerBar.animx = std::max(0.0, followerBar.animx);
1696 }
1697 else*/
1698 {
1699 real_t setpointDiffX = fpsScale * std::max(.01, (1.0 - followerBar.animx)) / 2.0;
1700 followerBar.animx += setpointDiffX;
1701 followerBar.animx = std::min(1.0, followerBar.animx);
1702 }
1703 }
1704
1705 SDL_Rect pos = entryFrame->getSize();
1706 pos.x = pos.w - followerBar.animx * pos.w;
1707 if ( halfWidthBars && !bPlayerBars )
1708 {
1709 pos.x += 8;
1710 }
1711 if ( updateTitleFrame )
1712 {
1713 pos.y = hud_t.allyFollowerTitleFrame->getSize().h - AllyStatusBarSettings_t::FollowerBars_t::entrySettings.entryHeight * 2;
1714 }
1715 else
1716 {
1717 pos.y = currentY;
1718 }
1719 if ( bPlayerBars )
1720 {
1721 if ( shortBars )
1722 {
1723 pos.h = AllyStatusBarSettings_t::PlayerBars_t::entrySettings.entryCompactHeight;
1724 }
1725 else
1726 {
1727 pos.h = AllyStatusBarSettings_t::PlayerBars_t::entrySettings.entryHeight;
1728 }
1729 }
1730 else
1731 {
1732 if ( shortBars )
1733 {
1734 pos.h = AllyStatusBarSettings_t::FollowerBars_t::entrySettings.entryCompactHeight;
1735 }
1736 else
1737 {
1738 pos.h = AllyStatusBarSettings_t::FollowerBars_t::entrySettings.entryHeight;
1739 }
1740 entryHeightForGlyphs = pos.h;
1741 }
1742 if ( bPlayerBars )
1743 {
1744 baseFrameHeight += pos.h;
1745 }
1746 else
1747 {
1748 if ( !infiniteScrolling )
1749 {
1750 if ( barIndex < kNumEntriesToShow )
1751 {
1752 baseFrameHeight += pos.h;
1753 }
1754 }
1755 else
1756 {
1757 if ( !shortBars && !updateTitleFrame )
1758 {
1759 baseFrameHeight = (((kNumEntriesToShow - 1))
1760 * AllyStatusBarSettings_t::FollowerBars_t::entrySettings.entryHeight);
1761 }
1762 else
1763 {
1764 baseFrameHeight = (((kNumEntriesToShow - 1))
1765 * AllyStatusBarSettings_t::FollowerBars_t::entrySettings.entryCompactHeight);
1766 }
1767 }
1768 }
1769 entryFrame->setOpacity(baseFrame->getOpacity());
1770
1771
1772 if ( followerBar.expired && ((ticks - followerBar.expiredTicks) > TICKS_PER_SECOND50 / 2) )
1773 {
1774 if ( doAnimation )
1775 {
1776 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
1777 real_t setpointDiffFade = fpsScale * std::max(.01, (1.0 - followerBar.animFade)) / 5.0;
1778 followerBar.animFade += setpointDiffFade;
1779 followerBar.animFade = std::min(1.0, followerBar.animFade);
1780 }
1781 entryFrame->setOpacity(entryFrame->getOpacity() * (1.0 - followerBar.animFade));
1782
1783 if ( doAnimation && followerBar.animFade >= 0.9999 )
1784 {
1785 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
1786 real_t setpointDiffY = fpsScale * std::max(.01, (1.0 - followerBar.animy)) / 2.0;
1787 followerBar.animy += setpointDiffY;
1788 followerBar.animy = std::min(1.0, followerBar.animy);
1789 }
1790 pos.h *= (1.0 - followerBar.animy);
1791 }
1792
1793 Uint32 uid = (*it).first;
1794 Entity* follower = bPlayerBars ? players[uid]->entity : uidToEntity(uid);
1795 if ( (!bPlayerBars && (!follower || followerUids.find(uid) == followerUids.end())) || (bPlayerBars && client_disconnected[uid]) )
1796 {
1797 if ( !followerBar.expired )
1798 {
1799 followerBar.expiredTicks = ticks;
1800 followerBar.animFadeScroll = 0.0;
1801 followerBar.animFadeScrollDummy = 1.0;
1802 }
1803 followerBar.expired = true;
1804 followerBar.selected = false;
1805 }
1806
1807 if ( bPlayerBars )
1808 {
1809 entryFrame->setUserData(followerBar.expired ? nullptr : (void*)(intptr_t)(uid + 1)); // 0 is nullptr, so +1
1810 }
1811
1812 if ( !bPlayerBars )
1813 {
1814 if ( doAnimation )
1815 {
1816 if ( hud_t.followerBars.size() > kNumEntriesToShow && !infiniteScrolling )
1817 {
1818 if ( barIndex >= followerDisplay.scrollSetpoint
1819 && barIndex < (followerDisplay.scrollSetpoint + kNumEntriesToShow) )
1820 {
1821 // visible in list
1822 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
1823 real_t setpointDiffFade = fpsScale * std::max(.01, followerBar.animFadeScroll) / 1.0;
1824 followerBar.animFadeScroll -= setpointDiffFade;
1825 followerBar.animFadeScroll = std::max(0.0, followerBar.animFadeScroll);
1826 }
1827 else
1828 {
1829 // not visible
1830 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
1831 real_t setpointDiffFade = fpsScale * std::max(.01, (1.0 - followerBar.animFadeScroll)) / 1.0;
1832 followerBar.animFadeScroll += setpointDiffFade;
1833 followerBar.animFadeScroll = std::min(1.0, followerBar.animFadeScroll);
1834 }
1835 }
1836 else
1837 {
1838 if ( infiniteScrolling
1839 && (barIndex < followerDisplay.scrollSetpoint
1840 // below statement fades real bars if the list is truncated because of JSON config.
1841 // if list not truncated, then no real bar should show up until scroll resets to 0
1842 || barIndex >= followerDisplay.scrollSetpoint + kNumEntriesToShow - 1) )
1843 {
1844 //followerBar.animFadeScroll = 1.0;
1845 // not visible
1846 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
1847 if ( !followerBar.dummy )
1848 {
1849 real_t setpointDiffFade = fpsScale * std::max(.01, (1.0 - followerBar.animFadeScroll)) / 10.0;
1850 followerBar.animFadeScroll += setpointDiffFade;
1851 followerBar.animFadeScroll = std::min(1.0, followerBar.animFadeScroll);
1852 }
1853
1854 }
1855 else
1856 {
1857 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
1858 if ( !followerBar.dummy )
1859 {
1860 real_t setpointDiffFade = fpsScale * std::max(.01, followerBar.animFadeScroll) / 10.0;
1861 followerBar.animFadeScroll -= setpointDiffFade;
1862 followerBar.animFadeScroll = std::max(0.0, followerBar.animFadeScroll);
1863 }
1864
1865 }
1866
1867 if ( infiniteScrolling
1868 && (barIndex < followerDisplay.scrollSetpoint - 1
1869 // below statement stops dummy bars fading in if the list is truncated because of JSON config.
1870 // otherwise fade in as the real bar is less than the current scroll
1871 && !(barIndex + realActiveBars >= followerDisplay.scrollSetpoint + kNumEntriesToShow - 1)) )
1872 {
1873 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
1874 if ( !followerBar.dummy )
1875 {
1876 real_t setpointDiffFade = fpsScale * std::max(.01, followerBar.animFadeScrollDummy) / 10.0;
1877 followerBar.animFadeScrollDummy -= setpointDiffFade;
1878 followerBar.animFadeScrollDummy = std::max(0.0, followerBar.animFadeScrollDummy);
1879 }
1880 }
1881 else
1882 {
1883 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
1884 if ( !followerBar.dummy )
1885 {
1886 real_t setpointDiffFade = fpsScale * std::max(.01, (1.0 - followerBar.animFadeScrollDummy)) / 10.0;
1887 followerBar.animFadeScrollDummy += setpointDiffFade;
1888 followerBar.animFadeScrollDummy = std::min(1.0, followerBar.animFadeScrollDummy);
1889 }
1890 }
1891 }
1892 }
1893
1894 if ( !updateTitleFrame )
1895 {
1896 bool fadeWhenMenuOpen = FollowerMenu[player].followerMenuIsOpen() && halfWidthBars;
1897 real_t dim = .5;
1898 if ( followerBar.dummy )
1899 {
1900 entryFrame->setOpacity(entryFrame->getOpacity() * (1.0 - followerBar.animFadeScrollDummy) * (fadeWhenMenuOpen ? dim : 1.0));
1901 }
1902 else
1903 {
1904 entryFrame->setOpacity(entryFrame->getOpacity() * (1.0 - followerBar.animFadeScroll) * (fadeWhenMenuOpen ? dim : 1.0));
1905 }
1906 }
1907 }
1908
1909 if ( !updateTitleFrame )
1910 {
1911 currentY += pos.h;
1912 }
1913 entryFrame->setSize(pos);
1914
1915 auto hpFrame = entryFrame->findFrame("hp");
1916 auto hpMid = hpFrame->findImage("hp img mid");
1917 auto hpEndcap = hpFrame->findImage("hp img endcap");
1918 auto hpProgressBase = hpFrame->findImage("hp img progress base");
1919 auto hpProgress = hpFrame->findImage("hp img progress");
1920 auto hpProgressEndcap = hpFrame->findImage("hp img progress endcap");
1921
1922 auto& hpBarSettings = bPlayerBars ? AllyStatusBarSettings_t::PlayerBars_t::hpBar : AllyStatusBarSettings_t::FollowerBars_t::hpBar;
1923 auto& mpBarSettings = bPlayerBars ? AllyStatusBarSettings_t::PlayerBars_t::mpBar : AllyStatusBarSettings_t::FollowerBars_t::mpBar;
1924 const int hpCompactMaxWidthAmount = hpBarSettings.barCompactMaxWidthAmount;
1925 const int hpMaxWidthAmount = hpBarSettings.barMaxWidthAmount;
1926 const real_t hpCompactWidthIncreasePercentOnInterval = hpBarSettings.barCompactWidthIncreasePercentOnInterval;
1927 const real_t hpWidthIncreasePercentOnInterval = hpBarSettings.barWidthIncreasePercentOnInterval;
1928 const int hpCompactIntervalToIncreaseWidth = hpBarSettings.barCompactIntervalToIncreaseWidth;
1929 const int hpIntervalToIncreaseWidth = hpBarSettings.barIntervalToIncreaseWidth;
1930 const int hpCompactBasePercentSize = hpBarSettings.barCompactBasePercentSize;
1931 const int hpBasePercentSize = hpBarSettings.barBasePercentSize;
1932
1933 const real_t hpCompactIntervalStartValue = hpBarSettings.barCompactIntervalStartValue;
1934 const real_t hpIntervalStartValue = hpBarSettings.barIntervalStartValue;
1935
1936 const int mpCompactMaxWidthAmount = mpBarSettings.barCompactMaxWidthAmount;
1937 const int mpMaxWidthAmount = mpBarSettings.barMaxWidthAmount;
1938 const real_t mpCompactWidthIncreasePercentOnInterval = mpBarSettings.barCompactWidthIncreasePercentOnInterval;
1939 const real_t mpWidthIncreasePercentOnInterval = mpBarSettings.barWidthIncreasePercentOnInterval;
1940 const int mpCompactIntervalToIncreaseWidth = mpBarSettings.barCompactIntervalToIncreaseWidth;
1941 const int mpIntervalToIncreaseWidth = mpBarSettings.barIntervalToIncreaseWidth;
1942 const int mpCompactBasePercentSize = mpBarSettings.barCompactBasePercentSize;
1943 const int mpBasePercentSize = mpBarSettings.barBasePercentSize;
1944
1945 const real_t mpCompactIntervalStartValue = mpBarSettings.barCompactIntervalStartValue;
1946 const real_t mpIntervalStartValue = mpBarSettings.barIntervalStartValue;
1947
1948 Stat* followerStats = nullptr;
1949 if ( bPlayerBars )
1950 {
1951 followerStats = stats[uid];
1952 }
1953 else
1954 {
1955 followerStats = follower ? follower->getStats() : nullptr;
1956 }
1957
1958 int MAXHP = HPBar.maxValue;
1959 int HP = HPBar.animateSetpoint;
1960 int MAXMP = MPBar.maxValue;
1961 int MP = MPBar.animateSetpoint;
1962 Field* nameField = entryFrame->findField("name");
1963 bool updateName = false;
1964 if ( followerBar.name == "" || followerDisplay.bHalfWidthBars != halfWidthBars || updateTitleFrame )
1965 {
1966 updateName = true;
1967 }
1968
1969 if ( followerStats && ((!bPlayerBars && follower) || bPlayerBars) )
1970 {
1971 followerBar.level = followerStats->LVL;
1972 if ( follower )
1973 {
1974 followerBar.monsterType = follower->getMonsterTypeFromSprite();
1975 followerBar.model = follower->sprite;
1976 }
1977 else
1978 {
1979 followerBar.monsterType = followerStats->type;
1980 }
1981
1982 if ( HP != followerStats->HP || MAXHP != followerStats->MAXHP )
1983 {
1984 updateName = true;
1985 }
1986 HP = followerStats->HP;
1987 MAXHP = followerStats->MAXHP;
1988
1989 MP = followerStats->MP;
1990 MAXMP = followerStats->MAXMP;
1991 }
1992 else
1993 {
1994 if ( !bPlayerBars )
1995 {
1996 HP = 0;
1997 }
1998 }
1999
2000 if ( updateName && (followerStats && ((!bPlayerBars && follower) || bPlayerBars)) )
2001 {
2002 std::string followerName = "";
2003 followerBar.customPortraitPath = "";
2004 size_t maxNameLen = 0;
2005 if ( bPlayerBars )
2006 {
2007 maxNameLen = AllyStatusBarSettings_t::PlayerBars_t::entrySettings.maxNameLengthFullsize;
2008 }
2009 else
2010 {
2011 if ( !halfWidthBars )
2012 {
2013 if ( !shortBars )
2014 {
2015 maxNameLen = AllyStatusBarSettings_t::FollowerBars_t::entrySettings.maxNameLengthFullsize;
2016 }
2017 else
2018 {
2019 maxNameLen = AllyStatusBarSettings_t::FollowerBars_t::entrySettings.maxNameLengthCompact;
2020 }
2021 }
2022 else
2023 {
2024 if ( !shortBars )
2025 {
2026 maxNameLen = AllyStatusBarSettings_t::FollowerBars_t::entrySettings.maxNameLengthSplitscreenFullsize;
2027 }
2028 else
2029 {
2030 maxNameLen = AllyStatusBarSettings_t::FollowerBars_t::entrySettings.maxNameLengthSplitscreenCompact;
2031 }
2032 }
2033 if ( !shortBars )
2034 {
2035 if ( MAXHP >= 100 || HP >= 100 )
2036 {
2037 maxNameLen -= 1;
2038 }
2039 if ( MAXHP >= 100 && HP >= 100 )
2040 {
2041 maxNameLen -= 1;
2042 }
2043 if ( MAXHP >= 200 && HP >= 200 )
2044 {
2045 maxNameLen -= 1;
2046 }
2047 }
2048 }
2049
2050 if ( bPlayerBars )
2051 {
2052 followerName = followerStats->name;
2053 if ( followerName.size() > maxNameLen )
2054 {
2055 followerName = followerName.substr(0, maxNameLen - 1);
2056 followerName += "..";
2057 }
2058 }
2059 else if ( strcmp(followerStats->name, "") && strcmp(followerStats->name, "nothing") )
2060 {
2061 if ( strlen(followerStats->name) > maxNameLen )
2062 {
2063 if ( followerStats->type == SKELETON )
2064 {
2065 if ( MonsterData_t::nameMatchesSpecialNPCName(*followerStats, "skeleton sentinel") )
2066 {
2067 followerName = MonsterData_t::monsterDataEntries[SKELETON].specialNPCs["skeleton sentinel"].shortname;
2068 followerBar.customPortraitPath = MonsterData_t::monsterDataEntries[SKELETON].specialNPCs["skeleton sentinel"].uniqueIcon;
2069 }
2070 else if ( MonsterData_t::nameMatchesSpecialNPCName(*followerStats, "skeleton knight") )
2071 {
2072 followerName = MonsterData_t::monsterDataEntries[SKELETON].specialNPCs["skeleton knight"].shortname;
2073 followerBar.customPortraitPath = MonsterData_t::monsterDataEntries[SKELETON].specialNPCs["skeleton knight"].uniqueIcon;
2074 }
2075 else
2076 {
2077 followerName = followerStats->name;
2078 }
2079 }
2080 else
2081 {
2082 followerName = followerStats->name;
2083 }
2084
2085 if ( followerName.size() > maxNameLen )
2086 {
2087 followerName = followerName.substr(0, maxNameLen - 1);
2088 followerName += "..";
2089 }
2090 }
2091 else
2092 {
2093 followerName = followerStats->name;
2094 }
2095 }
2096 else
2097 {
2098 followerName = getMonsterLocalizedName(followerStats->type);
2099 if ( followerName.size() > maxNameLen )
2100 {
2101 if ( MonsterData_t::monsterDataEntries[followerStats->type].defaultShortDisplayName != "" )
2102 {
2103 followerName = MonsterData_t::monsterDataEntries[followerStats->type].defaultShortDisplayName;
2104 }
2105 if ( followerName.size() > maxNameLen )
2106 {
2107 followerName = followerName.substr(0, maxNameLen - 1);
2108 followerName += "..";
2109 }
2110 }
2111 }
2112 capitalizeString(followerName);
2113 followerBar.name = followerName;
2114 }
2115
2116 char levelbuf[32];
2117 snprintf(levelbuf, sizeof(levelbuf), "Lv%d", followerBar.level);
2118 auto levelField = entryFrame->findField("level");
2119 levelField->setText(levelbuf);
2120
2121 char buf[32];
2122 snprintf(buf, sizeof(buf), "%d/%d", HP, MAXHP);
2123 auto hpField = entryFrame->findField("hp");
2124 hpField->setText(buf);
2125
2126 if ( bPlayerBars )
2127 {
2128 static ConsoleVariable<bool> cvar_playerbars_use_colors("/playerbars_use_colors", true);
2129 if ( *cvar_playerbars_use_colors )
2130 {
2131 nameField->setColor(playerColor(uid, colorblind_lobby, false));
2132 }
2133 else
2134 {
2135 nameField->setColor(hudColors.characterSheetLightNeutral);
2136 }
2137 }
2138 else
2139 {
2140 nameField->setColor(hudColors.characterSheetLightNeutral);
2141 }
2142 nameField->setText(followerBar.name.c_str());
2143
2144 int hpWidth = (shortBars ? hpBarSettings.barCompactPixelWidth : hpBarSettings.barPixelWidth);
2145 if ( halfWidthBars )
2146 {
2147 hpWidth += hpBarSettings.barSplitscreenPixelWidthOffset;
2148 }
2149
2150 Frame* portrait = entryFrame->findFrame("portrait");
2151 if ( shortBars )
2152 {
2153 portrait->setDisabled(true);
2154 SDL_Rect hpFramePos = hpFrame->getSize();
2155 hpFramePos.x = levelField->getSize().w - 40 - (hpWidth + 6);
2156 hpFramePos.y = 4;
2157 hpFrame->setSize(hpFramePos);
2158
2159 hpField->setDisabled(true);
2160
2161 nameField->setSize(SDL_Rect{ 0, 0, hpFramePos.x - 8, nameField->getSize().h });
2162 nameField->setHJustify(Field::justify_t::RIGHT);
2163
2164 levelField->setSize(SDL_Rect{ 0, nameField->getSize().y, entryFrame->getSize().w - 16, levelField->getSize().h });
2165
2166 if ( !followerDisplay.infiniteScrolling )
2167 {
2168 if ( auto textGet = nameField->getTextObject() )
2169 {
2170 int x = nameField->getSize().x + nameField->getSize().w - textGet->getWidth();
2171 selectorX = selectorX != 0 ? std::min(selectorX, x) : x;
2172 }
2173 }
2174 }
2175 else
2176 {
2177 portrait->setDisabled(false);
2178 SDL_Rect hpFramePos = hpFrame->getSize();
2179 hpFramePos.x = levelField->getSize().w - 40 - (hpWidth + 6);
2180 hpFramePos.y = 16;
2181 hpFrame->setSize(hpFramePos);
2182
2183 hpField->setDisabled(false);
2184
2185 portrait->setSize(SDL_Rect{ hpFramePos.x + 4 - portrait->getSize().w, 0, portrait->getSize().w, portrait->getSize().h });
2186 auto portraitImg = portrait->findImage("portrait img");
2187 if ( followerBar.customPortraitPath != "" )
2188 {
2189 portraitImg->path = followerBar.customPortraitPath;
2190 }
2191 else
2192 {
2193 portraitImg->path = monsterData.getAllyIconFromSprite(followerBar.model, followerBar.monsterType);
2194 }
2195
2196 nameField->setSize(SDL_Rect{ hpFramePos.x, 0, entryFrame->getSize().w - hpFramePos.x, nameField->getSize().h });
2197 nameField->setHJustify(Field::justify_t::LEFT);
2198
2199 levelField->setSize(SDL_Rect{ 0, 14, entryFrame->getSize().w - 16, levelField->getSize().h });
2200
2201 if ( !followerDisplay.infiniteScrolling )
2202 {
2203 selectorX = selectorX != 0 ? std::min(selectorX, portrait->getSize().x) : portrait->getSize().x;
2204 }
2205 }
2206
2207 if ( updateTitleFrame )
2208 {
2209 auto bgImgLeft = entryFrame->findImage("bg img left");
2210 bgImgLeft->disabled = false;
2211 auto bgImgMid = entryFrame->findImage("bg img mid");
2212 bgImgMid->disabled = false;
2213 auto bgImgRight = entryFrame->findImage("bg img right");
2214 bgImgRight->disabled = false;
2215
2216 const int totalWidth = entryFrame->getSize().w - portrait->getSize().x;
2217 bgImgMid->pos.w = totalWidth - bgImgLeft->pos.w - bgImgRight->pos.w;
2218 /*bgImg->pos.h = entryFrame->getSize().h;*/
2219 bgImgLeft->pos.y = 0;
2220 bgImgMid->pos.y = 0;
2221 bgImgRight->pos.y = 0;
2222 bgImgLeft->pos.x = entryFrame->getSize().w - totalWidth - 8;
2223 bgImgMid->pos.x = bgImgLeft->pos.x + bgImgLeft->pos.w;
2224 bgImgRight->pos.x = bgImgMid->pos.x + bgImgMid->pos.w;
2225
2226 selectorX = bgImgLeft->pos.x;
2227 }
2228
2229 { // HP
2230 int backgroundWidth = hpWidth - 6;
2231 real_t progressWidth = hpWidth - 8;
2232
2233 // handle bar size changing
2234 {
2235 real_t multiplier = 1.0;
2236 const Sint32 maxHPWidth = (shortBars ? hpCompactMaxWidthAmount : hpMaxWidthAmount);
2237 if ( MAXHP < maxHPWidth )
2238 {
2239 // start at 30%, increase 2.5% every 5 HP past 20 MAXHP
2240 multiplier = (shortBars ? hpCompactBasePercentSize : hpBasePercentSize) / 100.0;
2241 real_t widthIntervalPercent = (shortBars ? hpCompactWidthIncreasePercentOnInterval : hpWidthIncreasePercentOnInterval) / 100.0;
2242 int intervalThreshold = (shortBars ? hpCompactIntervalToIncreaseWidth : hpIntervalToIncreaseWidth);
2243 int baseIntervalStart = (shortBars ? hpCompactIntervalStartValue : hpIntervalStartValue);
2244 multiplier += (widthIntervalPercent * ((std::max(0, MAXHP - baseIntervalStart) / intervalThreshold)));
2245 }
2246
2247 int diff = static_cast<int>(std::max(0.0, progressWidth - progressWidth * multiplier)); // how many pixels the progress bar shrinks
2248 progressWidth -= diff; // scale the progress bars
2249 hpMid->pos.w = backgroundWidth - diff; // move the background bar by x pixels as above
2250 hpEndcap->pos.x = hpMid->pos.x + hpMid->pos.w; // move the background endcap by x pixels as above
2251 }
2252
2253 HPBar.animatePreviousSetpoint = HPBar.animateSetpoint;
2254 real_t& hpForegroundValue = HPBar.animateValue;
2255 real_t& hpFadedValue = HPBar.animateValue2;
2256
2257 HPBar.animateSetpoint = HP;
2258 if ( HPBar.animateSetpoint < HPBar.animatePreviousSetpoint ) // insta-change as losing health
2259 {
2260 hpForegroundValue = HPBar.animateSetpoint;
2261 HPBar.animateTicks = ticks;
2262
2263 // flash for taking damage
2264 HPBar.flashTicks = ticks;
2265 HPBar.flashAnimState = -1;
2266 HPBar.flashType = Player::HUD_t::FLASH_ON_DAMAGE;
2267 }
2268
2269 if ( HPBar.maxValue > MAXHP )
2270 {
2271 hpFadedValue = HPBar.animateSetpoint; // resetting game etc, stop fade animation sticking out of frame
2272 }
2273
2274 HPBar.maxValue = MAXHP;
2275 if ( !followerBar.bInit )
2276 {
2277 hpForegroundValue = HPBar.animateSetpoint;
2278 }
2279 if ( hpForegroundValue < HPBar.animateSetpoint ) // gaining HP, animate
2280 {
2281 // flash for gaining HP, provided not already flashing
2282 /*if ( HPBar.flashAnimState == -1 || (HPBar.flashAnimState >= 0 && HPBar.flashType != FLASH_ON_DAMAGE) )
2283 {
2284 HPBar.flashTicks = ticks;
2285 HPBar.flashAnimState = -1;
2286 HPBar.flashType = FLASH_ON_RECOVERY;
2287 }*/
2288
2289 if ( doAnimation )
2290 {
2291 real_t setpointDiff = std::max(0.0, HPBar.animateSetpoint - hpForegroundValue);
2292 real_t fpsScale = getFPSScale(144.0);
2293 hpForegroundValue += fpsScale * (setpointDiff / 20.0); // reach it in 20 intervals, scaled to FPS
2294 }
2295 hpForegroundValue = std::min(static_cast<real_t>(HPBar.animateSetpoint), hpForegroundValue);
2296
2297 if ( abs(HPBar.animateSetpoint) - abs(hpForegroundValue) <= 1.0 )
2298 {
2299 hpForegroundValue = HPBar.animateSetpoint;
2300 }
2301
2302 /* int increment = 3;
2303 double scaledIncrement = (increment * (getFPSScale(144.0)));*/
2304 //real_t diff = std::max(.1, (HPBar.animateSetpoint * 10 - hpForegroundValue) / (maxValue / 5)); // 0.1-5 value
2305 //if ( HPBar.animateSetpoint * 10 >= maxValue )
2306 //{
2307 // diff = 5;
2308 //}
2309 //scaledIncrement *= 0.2 * pow(diff, 2) + .5;
2310
2311 //hpForegroundValue = std::min(HPBar.animateSetpoint * 10.0, hpForegroundValue + scaledIncrement);
2312 //messagePlayer(0, "%.2f | %.2f", hpForegroundValue);
2313 }
2314 else if ( hpForegroundValue > HPBar.animateSetpoint ) // losing HP, snap to value
2315 {
2316 hpForegroundValue = HPBar.animateSetpoint;
2317 }
2318
2319 if ( hpFadedValue < HPBar.animateSetpoint )
2320 {
2321 hpFadedValue = hpForegroundValue;
2322 HPBar.animateTicks = ticks;
2323 }
2324 else if ( hpFadedValue > HPBar.animateSetpoint )
2325 {
2326 if ( ticks - HPBar.animateTicks > 30 /*|| stats[player.playernum]->HP <= 0*/ ) // fall after x ticks
2327 {
2328 if ( doAnimation )
2329 {
2330 real_t setpointDiff = std::max(0.01, hpFadedValue - HPBar.animateSetpoint);
2331 real_t fpsScale = getFPSScale(144.0);
2332 hpFadedValue -= fpsScale * (setpointDiff / 20.0); // reach it in 20 intervals, scaled to FPS
2333 }
2334 hpFadedValue = std::max(static_cast<real_t>(HPBar.animateSetpoint), hpFadedValue);
2335 }
2336 }
2337 else
2338 {
2339 HPBar.animateTicks = ticks;
2340 }
2341
2342 real_t foregroundPercent = hpForegroundValue / HPBar.maxValue;
2343 hpProgress->pos.w = std::max(1, static_cast<int>((progressWidth)* foregroundPercent));
2344 hpProgressEndcap->pos.x = hpProgress->pos.x + hpProgress->pos.w;
2345
2346 auto hpProgressEndcapDamaged = hpFrame->findImage("hp img progress damaged");
2347 hpProgressEndcapDamaged->disabled = true;
2348 hpProgressEndcapDamaged->pos.x = hpProgressEndcap->pos.x + (hpProgressEndcap->pos.w - hpProgressEndcapDamaged->pos.w);
2349 if ( hpForegroundValue < HPBar.maxValue ) // damaged
2350 {
2351 hpProgressEndcapDamaged->disabled = false;
2352 }
2353
2354 real_t fadePercent = hpFadedValue / HPBar.maxValue;
2355 if ( HP <= 0 )
2356 {
2357 // hide all the progress elements when dead, as endcap/base don't shrink
2358 // hpProgress width 0px defaults to original size, so hide that too
2359 hpProgress->disabled = true;
2360 hpProgressEndcap->disabled = true;
2361 hpProgressBase->disabled = true;
2362 hpProgressEndcapDamaged->disabled = true;
2363 }
2364 else
2365 {
2366 hpProgress->disabled = false;
2367 hpProgressEndcap->disabled = false;
2368 hpProgressBase->disabled = false;
2369 }
2370
2371 auto hpProgressEndCapFlash = hpFrame->findImage("hp img progress endcap flash");
2372 hpProgressEndCapFlash->disabled = true;
2373 const int framesPerAnimation = HPBar.flashType == Player::HUD_t::FLASH_ON_DAMAGE ? 1 : 2;
2374 const int numAnimationFrames = HPBar.flashType == Player::HUD_t::FLASH_ON_DAMAGE ? 20 : 2;
2375 if ( HPBar.flashTicks > 0 )
2376 {
2377 //messagePlayer(0, MESSAGE_DEBUG, "ticks: %d, animticks: %d, state: %d", ticks, HPBarFlashTicks, HPBarFlashAnimState);
2378 if ( HPBar.flashAnimState > numAnimationFrames || hpProgress->disabled )
2379 {
2380 HPBar.flashTicks = 0;
2381 HPBar.flashType = Player::HUD_t::FLASH_ON_DAMAGE;
2382 HPBar.flashAnimState = -1;
2383 hpProgressEndCapFlash->disabled = true;
2384 }
2385 else
2386 {
2387 hpProgressEndCapFlash->disabled = hpProgressEndcap->disabled;
2388 if ( ticks == HPBar.flashTicks )
2389 {
2390 HPBar.flashAnimState = 1;
2391 HPBar.flashProcessedOnTick = ticks;
2392 }
2393 else if ( (HPBar.flashProcessedOnTick != ticks)
2394 && (ticks > HPBar.flashTicks)
2395 && (ticks - HPBar.flashTicks) % framesPerAnimation == 0 )
2396 {
2397 ++HPBar.flashAnimState;
2398 HPBar.flashProcessedOnTick = ticks;
2399 }
2400
2401 if ( HPBar.flashType == Player::HUD_t::FLASH_ON_DAMAGE )
2402 {
2403 if ( HPBar.flashAnimState <= 6 )
2404 {
2405 hpProgressEndCapFlash->color = 0xFFFFFFFF;
2406 }
2407
2408 if ( HPBar.flashAnimState == 0 )
2409 {
2410 hpProgress->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Mid_00.png";
2411 hpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_F00.png";
2412 hpProgressBase->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Base_00.png";
2413 }
2414 else if ( HPBar.flashAnimState >= 1 && HPBar.flashAnimState <= 2 )
2415 {
2416 hpProgress->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Mid_01.png";
2417 hpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_F01.png";
2418 hpProgressBase->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Base_01.png";
2419 }
2420 else if ( HPBar.flashAnimState == 3 )
2421 {
2422 hpProgress->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Mid_02.png";
2423 hpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_F02.png";
2424 hpProgressBase->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Base_02.png";
2425 }
2426 else if ( HPBar.flashAnimState >= 4 && HPBar.flashAnimState <= 5 )
2427 {
2428 hpProgress->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Mid_01.png";
2429 hpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_F01.png";
2430 hpProgressBase->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Base_01.png";
2431 }
2432 else if ( HPBar.flashAnimState == 6 )
2433 {
2434 hpProgress->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Mid_03.png";
2435 hpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_F03.png";
2436 hpProgressBase->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Base_03.png";
2437 }
2438 else
2439 {
2440 hpProgress->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Mid_00.png";
2441 hpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_F00.png";
2442 hpProgressBase->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Base_00.png";
2443 Uint8 r, g, b, a;
2444 getColor(hpProgressEndCapFlash->color, &r, &g, &b, &a);
2445 int decrement = 20;
2446 real_t fpsScale = getFPSScale(60.0);
2447 decrement *= fpsScale;
2448 a = std::max(0, (int)a - decrement);
2449 hpProgressEndCapFlash->color = makeColor(r, g, b, a);
2450 }
2451 }
2452 else
2453 {
2454 hpProgressEndCapFlash->color = 0xFFFFFFFF;
2455 hpProgressEndCapFlash->disabled = true;
2456 if ( HPBar.flashAnimState == 1 )
2457 {
2458 hpProgress->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Mid_03.png";
2459 hpProgressBase->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Base_03.png";
2460 hpProgressEndcap->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_03.png";
2461 }
2462 }
2463 }
2464 }
2465 else
2466 {
2467 HPBar.flashAnimState = -1;
2468 hpProgressEndCapFlash->disabled = true;
2469 hpProgress->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Mid_00.png";
2470 hpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_End_F00.png";
2471 hpProgressBase->path = "*#images/ui/HUD/allies/HUD_HPBar_Fill_Base_00.png";
2472 }
2473 if ( !hpProgressEndcap->disabled )
2474 {
2475 hpProgressEndCapFlash->pos.x = hpProgressEndcap->pos.x - (hpProgressEndCapFlash->pos.w - hpProgressEndcap->pos.w);
2476 }
2477 }
2478
2479 auto mpFrame = entryFrame->findFrame("mp");
2480 if ( mpFrame )
2481 { // MP
2482 auto mpMid = mpFrame->findImage("mp img mid");
2483 auto mpEndcap = mpFrame->findImage("mp img endcap");
2484 auto mpProgressBase = mpFrame->findImage("mp img progress base");
2485 auto mpProgress = mpFrame->findImage("mp img progress");
2486 auto mpProgressEndcap = mpFrame->findImage("mp img progress endcap");
2487
2488 const int mpWidth = (shortBars ? mpBarSettings.barCompactPixelWidth : mpBarSettings.barPixelWidth);
2489 int backgroundWidth = mpWidth - 2;
2490 real_t progressWidth = mpWidth - 4;
2491 // handle bar size changing
2492 {
2493 real_t multiplier = 1.0;
2494 const Sint32 maxMPWidth = (shortBars ? mpCompactMaxWidthAmount : mpMaxWidthAmount);
2495 if ( MAXMP < maxMPWidth )
2496 {
2497 // start at 30%, increase 2.5% every 5 MP past 20 MAXMP
2498 multiplier = (shortBars ? mpCompactBasePercentSize : mpBasePercentSize) / 100.0;
2499 real_t widthIntervalPercent = (shortBars ? mpCompactWidthIncreasePercentOnInterval : mpWidthIncreasePercentOnInterval) / 100.0;
2500 int intervalThreshold = (shortBars ? mpCompactIntervalToIncreaseWidth : mpIntervalToIncreaseWidth);
2501 int baseIntervalStart = (shortBars ? mpCompactIntervalStartValue : mpIntervalStartValue);
2502 multiplier += (widthIntervalPercent * ((std::max(0, MAXMP - baseIntervalStart) / intervalThreshold)));
2503 }
2504
2505 int diff = static_cast<int>(std::max(0.0, progressWidth - progressWidth * multiplier)); // how many pixels the progress bar shrinks
2506 progressWidth -= diff; // scale the progress bars
2507 mpMid->pos.w = backgroundWidth - diff; // move the background bar by x pixels as above
2508 mpEndcap->pos.x = mpMid->pos.x + mpMid->pos.w; // move the background endcap by x pixels as above
2509 }
2510
2511 MPBar.animatePreviousSetpoint = MPBar.animateSetpoint;
2512 real_t& mpForegroundValue = MPBar.animateValue;
2513 real_t& mpFadedValue = MPBar.animateValue2;
2514
2515 MPBar.animateSetpoint = MP;
2516 if ( MPBar.animateSetpoint < MPBar.animatePreviousSetpoint ) // insta-change as losing health
2517 {
2518 mpForegroundValue = MPBar.animateSetpoint;
2519 MPBar.animateTicks = ticks;
2520
2521 // flash for taking damage
2522 MPBar.flashTicks = ticks;
2523 MPBar.flashProcessedOnTick = 0;
2524 MPBar.flashAnimState = -1;
2525 MPBar.flashType = Player::HUD_t::FLASH_ON_DAMAGE;
2526 }
2527
2528 if ( MPBar.maxValue > MAXMP )
2529 {
2530 mpFadedValue = MPBar.animateSetpoint; // resetting game etc, stop fade animation sticking out of frame
2531 }
2532
2533 MPBar.maxValue = MAXMP;
2534 if ( !followerBar.bInit )
2535 {
2536 mpForegroundValue = MPBar.animateSetpoint;
2537 }
2538 if ( mpForegroundValue < MPBar.animateSetpoint ) // gaining MP, animate
2539 {
2540 // flash for gaining MP, provided not already flashing
2541 /*if ( MPBar.flashAnimState == -1 || (MPBar.flashAnimState >= 0 && MPBar.flashType != FLASH_ON_DAMAGE) )
2542 {
2543 MPBar.flashTicks = ticks;
2544 MPBar.flashAnimState = -1;
2545 MPBar.flashType = FLASH_ON_RECOVERY;
2546 }*/
2547 if ( doAnimation )
2548 {
2549 real_t setpointDiff = std::max(.1, MPBar.animateSetpoint - mpForegroundValue);
2550 real_t fpsScale = getFPSScale(144.0);
2551 mpForegroundValue += fpsScale * (setpointDiff / 20.0); // reach it in 20 intervals, scaled to FPS
2552 }
2553 mpForegroundValue = std::min(static_cast<real_t>(MPBar.animateSetpoint), mpForegroundValue);
2554
2555 /*if ( abs(MPBar.animateSetpoint) - abs(mpForegroundValue) <= 1.0 )
2556 {
2557 mpForegroundValue = MPBar.animateSetpoint;
2558 }*/
2559 }
2560 else if ( mpForegroundValue > MPBar.animateSetpoint ) // losing MP, snap to value
2561 {
2562 mpForegroundValue = MPBar.animateSetpoint;
2563 }
2564
2565 if ( mpFadedValue < MPBar.animateSetpoint )
2566 {
2567 mpFadedValue = mpForegroundValue;
2568 MPBar.animateTicks = ticks;
2569 }
2570 else if ( mpFadedValue > MPBar.animateSetpoint )
2571 {
2572 if ( ticks - MPBar.animateTicks > 30 /*|| stats[player.playernum]->MP <= 0*/ ) // fall after x ticks
2573 {
2574 if ( doAnimation )
2575 {
2576 real_t setpointDiff = std::max(0.1, mpFadedValue - MPBar.animateSetpoint);
2577 real_t fpsScale = getFPSScale(144.0);
2578 mpFadedValue -= fpsScale * (setpointDiff / 20.0); // reach it in 20 intervals, scaled to FPS
2579 }
2580 mpFadedValue = std::max(static_cast<real_t>(MPBar.animateSetpoint), mpFadedValue);
2581 }
2582 }
2583 else
2584 {
2585 MPBar.animateTicks = ticks;
2586 }
2587
2588 real_t foregroundPercent = mpForegroundValue / MPBar.maxValue;
2589 mpProgress->pos.w = std::max(1, static_cast<int>((progressWidth)* foregroundPercent));
2590 mpProgressEndcap->pos.x = mpProgress->pos.x + mpProgress->pos.w;
2591
2592 auto mpProgressEndcapDamaged = mpFrame->findImage("mp img progress damaged");
2593 mpProgressEndcapDamaged->disabled = true;
2594 mpProgressEndcapDamaged->pos.x = mpProgressEndcap->pos.x + (mpProgressEndcap->pos.w - mpProgressEndcapDamaged->pos.w);
2595 if ( mpForegroundValue < MPBar.maxValue ) // damaged
2596 {
2597 mpProgressEndcapDamaged->disabled = false;
2598 }
2599
2600 real_t fadePercent = mpFadedValue / MPBar.maxValue;
2601 if ( MP <= 0 )
2602 {
2603 // hide all the progress elements when dead, as endcap/base don't shrink
2604 // mpProgress width 0px defaults to original size, so hide that too
2605 mpProgress->disabled = true;
2606 mpProgressEndcap->disabled = true;
2607 mpProgressBase->disabled = true;
2608 mpProgressEndcapDamaged->disabled = true;
2609 }
2610 else
2611 {
2612 mpProgress->disabled = false;
2613 mpProgressEndcap->disabled = false;
2614 mpProgressBase->disabled = false;
2615 }
2616
2617 auto mpProgressEndCapFlash = mpFrame->findImage("mp img progress endcap flash");
2618 mpProgressEndCapFlash->disabled = true;
2619 const int framesPerAnimation = MPBar.flashType == Player::HUD_t::FLASH_ON_DAMAGE ? 1 : 2;
2620 const int numAnimationFrames = MPBar.flashType == Player::HUD_t::FLASH_ON_DAMAGE ? 30 : 2;
2621 if ( MPBar.flashTicks > 0 )
2622 {
2623 //messagePlayer(0, MESSAGE_DEBUG, "ticks: %d, animticks: %d, state: %d", ticks, MPBarFlashTicks, MPBarFlashAnimState);
2624 if ( MPBar.flashAnimState > numAnimationFrames || mpProgress->disabled )
2625 {
2626 MPBar.flashTicks = 0;
2627 MPBar.flashType = Player::HUD_t::FLASH_ON_DAMAGE;
2628 MPBar.flashAnimState = -1;
2629 mpProgressEndCapFlash->disabled = true;
2630 }
2631 else
2632 {
2633 mpProgressEndCapFlash->disabled = mpProgressEndcap->disabled;
2634 bool processedOnTick = MPBar.flashProcessedOnTick == ticks;
2635 if ( ticks == MPBar.flashTicks )
2636 {
2637 MPBar.flashAnimState = 1;
2638 MPBar.flashProcessedOnTick = ticks;
2639 }
2640 else if ( (!processedOnTick)
2641 && (ticks > MPBar.flashTicks)
2642 && (ticks - MPBar.flashTicks) % framesPerAnimation == 0 )
2643 {
2644 ++MPBar.flashAnimState;
2645 MPBar.flashProcessedOnTick = ticks;
2646 }
2647
2648 if ( MPBar.flashType == Player::HUD_t::FLASH_ON_DAMAGE )
2649 {
2650 if ( MPBar.flashAnimState <= 16 && MPBar.flashAnimState >= 10 )
2651 {
2652 mpProgressEndCapFlash->color = 0xFFFFFFFF;
2653 }
2654 else if ( MPBar.flashAnimState == 0 )
2655 {
2656 mpProgressEndCapFlash->color = makeColor(255, 255, 255, 0);
2657 mpProgressEndCapFlash->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_MPEnd_F00.png";
2658 }
2659
2660 if ( MPBar.flashAnimState <= 9 )
2661 {
2662 if ( MPBar.flashAnimState == 7 )
2663 {
2664 // we need the MP bar to flash long enough for long spellcast times
2665 // can adjust how many animStates we skip here to play with timing,
2666 // without changing other state machine code
2667 MPBar.flashAnimState = 9; // 1, 2 skip a few..
2668 }
2669 Uint8 r, g, b, a;
2670 getColor(mpProgressEndCapFlash->color, &r, &g, &b, &a);
2671 int increment = 10;
2672 real_t fpsScale = getFPSScale(60.0);
2673 increment *= fpsScale;
2674 a = std::min(255, (int)a + increment);
2675 mpProgressEndCapFlash->color = makeColor(r, g, b, a);
2676
2677 mpProgress->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Mid_00.png";
2678 mpProgressBase->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_F00.png";
2679
2680 if ( MPBar.flashAnimState % 2 == 0
2681 && !processedOnTick )
2682 {
2683 mpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_F00.png";
2684 }
2685 }
2686 else if ( MPBar.flashAnimState <= 10 )
2687 {
2688 mpProgress->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Mid_00.png";
2689 mpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_F00.png";
2690 mpProgressBase->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Base_00.png";
2691 }
2692 else if ( MPBar.flashAnimState >= 11 && MPBar.flashAnimState <= 12 )
2693 {
2694 mpProgress->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Mid_01.png";
2695 mpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_F01.png";
2696 mpProgressBase->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Base_01.png";
2697 }
2698 else if ( MPBar.flashAnimState == 13 )
2699 {
2700 mpProgress->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Mid_02.png";
2701 mpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_F02.png";
2702 mpProgressBase->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Base_02.png";
2703 }
2704 else if ( MPBar.flashAnimState >= 14 && MPBar.flashAnimState <= 15 )
2705 {
2706 mpProgress->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Mid_01.png";
2707 mpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_F01.png";
2708 mpProgressBase->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Base_01.png";
2709 }
2710 else if ( MPBar.flashAnimState == 16 )
2711 {
2712 mpProgress->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Mid_03.png";
2713 mpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_F03.png";
2714 mpProgressBase->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Base_03.png";
2715 }
2716 else
2717 {
2718 mpProgress->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Mid_00.png";
2719 mpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_F00.png";
2720 mpProgressBase->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Base_00.png";
2721 Uint8 r, g, b, a;
2722 getColor(mpProgressEndCapFlash->color, &r, &g, &b, &a);
2723 int decrement = 20;
2724 real_t fpsScale = getFPSScale(60.0);
2725 decrement *= fpsScale;
2726 a = std::max(0, (int)a - decrement);
2727 mpProgressEndCapFlash->color = makeColor(r, g, b, a);
2728 }
2729 }
2730 else
2731 {
2732 mpProgressEndCapFlash->color = 0xFFFFFFFF;
2733 mpProgressEndCapFlash->disabled = true;
2734 if ( MPBar.flashAnimState == 1 )
2735 {
2736 mpProgress->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Mid_03.png";
2737 mpProgressBase->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Base_03.png";
2738 mpProgressEndcap->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_03.png";
2739 }
2740 }
2741 }
2742 }
2743 else
2744 {
2745 MPBar.flashAnimState = -1;
2746 mpProgress->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Mid_00.png";
2747 mpProgressEndCapFlash->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_End_F00.png";
2748 mpProgressBase->path = "*#images/ui/HUD/allies/HUD_MPBar_Fill_Base_00.png";
2749 mpProgressEndCapFlash->disabled = true;
2750 }
2751 if ( !mpProgressEndcap->disabled )
2752 {
2753 mpProgressEndCapFlash->pos.x = mpProgressEndcap->pos.x - (mpProgressEndCapFlash->pos.w - mpProgressEndcap->pos.w);
2754 }
2755 }
2756 followerBar.bInit = true;
2757 if ( followerBar.selected && !titleFrame )
2758 {
2759 if ( selector )
2760 {
2761 SDL_Color color;
2762 getColor(selector->color, &color.r, &color.g, &color.b, &color.a);
2763 selector->color = makeColor(color.r, color.g, color.b, 255 * entryFrame->getOpacity() / 100.0);
2764 selector->disabled = false;
2765 if ( auto imgGet = Image::get(selector->path.c_str()) )
2766 {
2767 selector->pos.w = imgGet->getWidth();
2768 selector->pos.h = imgGet->getHeight();
2769 }
2770 selector->pos.x = entryFrame->getSize().x + selectorX - 8 - selector->pos.w;
2771 selector->pos.y = entryFrame->getSize().y + entryFrame->getSize().h / 2 - selector->pos.h / 2;
2772 }
2773 }
2774 else if ( followerBar.selected && titleFrame && updateTitleFrame )
2775 {
2776 if ( titleSelector )
2777 {
2778 SDL_Color color;
2779 getColor(titleSelector->color, &color.r, &color.g, &color.b, &color.a);
2780 titleSelector->color = makeColor(color.r, color.g, color.b, 255 * entryFrame->getOpacity() / 100.0);
2781 titleSelector->disabled = false;
2782 if ( auto imgGet = Image::get(titleSelector->path.c_str()) )
2783 {
2784 titleSelector->pos.w = imgGet->getWidth();
2785 titleSelector->pos.h = imgGet->getHeight();
2786 }
2787 titleSelector->pos.y = entryFrame->getSize().y + entryFrame->getSize().h / 2 - titleSelector->pos.h / 2;
2788 titleSelector->pos.x = entryFrame->getSize().x + selectorX - titleSelector->pos.w;
2789 const int selectorAnimW = 4;
2790 if ( titleSelectorGlyph )
2791 {
2792 if ( halfWidthBars
2793 || (!players[player]->shootmode && !FollowerMenu[player].followerMenuIsOpen() && !CalloutMenu[player].calloutMenuIsOpen())
2794 || followerDisplay.bCycleNextDisabled || list_Size(&stats[player]->FOLLOWERS) <= 1 )
2795 {
2796 titleSelectorGlyph->disabled = true;
2797 }
2798 else
2799 {
2800 titleSelectorGlyph->path = Input::inputs[player].getGlyphPathForBinding("Cycle NPCs");
2801 if ( auto imgGet = Image::get(titleSelectorGlyph->path.c_str()) )
2802 {
2803 titleSelectorGlyph->disabled = false;
2804 titleSelectorGlyph->pos.w = imgGet->getWidth();
2805 titleSelectorGlyph->pos.h = imgGet->getHeight();
2806 titleSelectorGlyph->pos.x = titleSelector->pos.x - 2 * selectorAnimW - titleSelectorGlyph->pos.w;
2807 titleSelectorGlyph->pos.y = titleSelector->pos.y + titleSelector->pos.h / 2 - titleSelectorGlyph->pos.h / 2;
2808
2809 static ConsoleVariable<int> followerMenuOpenGlyphX("/followermenu_open_glyph_x", 6);
2810 static ConsoleVariable<int> followerMenuOpenGlyphY("/followermenu_open_glyph_y", 0);
2811 titleSelectorGlyph->pos.x += *followerMenuOpenGlyphX;
2812 titleSelectorGlyph->pos.y += *followerMenuOpenGlyphY;
2813 if ( titleSelectorGlyph->pos.y % 2 == 1 )
2814 {
2815 --titleSelectorGlyph->pos.y;
2816 }
2817 }
2818 }
2819 }
2820
2821 titleSelector->pos.x += 4 - selectorAnimW * (cos(followerDisplay.animSelected * 2 * PI3.14159265358979323846));
2822 }
2823 }
2824
2825 if ( followerBar.selected && titleFrame && !doneTitleFrame )
2826 {
2827 updateTitleFrame = true;
2828 --barIndex;
2829 }
2830 else
2831 {
2832 updateTitleFrame = false;
2833 ++it;
2834 }
2835 }
2836
2837 followerDisplay.bHalfWidthBars = halfWidthBars;
2838
2839 SDL_Rect baseFramePos = baseFrame->getSize();
2840 baseFramePos.h = baseFrameHeight;
2841 baseFrame->setSize(baseFramePos);
2842 baseFrame->setActualSize(SDL_Rect{
2843 baseFrame->getActualSize().x,
2844 baseFrame->getActualSize().y,
2845 baseFrame->getActualSize().w ,
2846 infiniteScrolling ? baseFramePos.h * 10 : currentY });
2847
2848
2849 if ( !bPlayerBars && titleFrame && hud_t.allyFollowerGlyphFrame && !hud_t.allyFollowerGlyphFrame->isDisabled() )
2850 {
2851 static ConsoleVariable<int> followerMenuCommandGlyphX("/followermenu_cmd_glyph_x", -96);
2852 static ConsoleVariable<int> followerMenuCommandGlyphY("/followermenu_cmd_glyph_y", 0);
2853 static ConsoleVariable<int> followerMenuRepeatGlyphX("/followermenu_repeat_glyph_x", 0);
2854 static ConsoleVariable<int> followerMenuRepeatGlyphY("/followermenu_repeat_glyph_y", 0);
2855
2856 auto glyphFrame = hud_t.allyFollowerGlyphFrame;
2857 SDL_Rect glyphFramePos{ baseFrame->getSize().x, titleFrame->getSize().y, 150, 80 };
2858 glyphFramePos.x += *followerMenuCommandGlyphX;
2859 if ( !titleSelectorGlyph->disabled )
2860 {
2861 glyphFramePos.x -= titleSelectorGlyph->pos.w;
2862 }
2863 glyphFramePos.y += *followerMenuCommandGlyphY;
2864 glyphFrame->setSize(glyphFramePos);
2865
2866 auto commandGlyph = glyphFrame->findImage("glyph command");
2867 auto commandImg = glyphFrame->findImage("img command");
2868 //auto commandText = glyphFrame->findField("command prompt txt");
2869
2870 auto repeatGlyph = glyphFrame->findImage("glyph repeat");
2871 auto repeatImg = glyphFrame->findImage("img repeat");
2872 //auto repeatCmdText = glyphFrame->findField("repeat prompt txt");
2873
2874 if ( auto imgGet = Image::get(commandImg->path.c_str()) )
2875 {
2876 commandImg->disabled = false;
2877 commandImg->pos.w = imgGet->getWidth();
2878 commandImg->pos.h = imgGet->getHeight();
2879 commandImg->pos.x = glyphFramePos.w - commandImg->pos.w;
2880 commandImg->pos.y = 0;
2881
2882 commandGlyph->path = Input::inputs[player].getGlyphPathForBinding("Show NPC Commands");
2883 if ( auto imgGet = Image::get(commandGlyph->path.c_str()) )
2884 {
2885 commandGlyph->disabled = commandImg->disabled || followerDisplay.bOpenFollowerMenuDisabled;
2886 commandGlyph->pos.w = imgGet->getWidth();
2887 commandGlyph->pos.h = imgGet->getHeight();
2888 commandGlyph->pos.x = commandImg->pos.x - commandGlyph->pos.w;
2889 commandGlyph->pos.y = commandImg->pos.y + commandImg->pos.h / 2 - commandGlyph->pos.h / 2;
2890 }
2891 else
2892 {
2893 commandImg->disabled = true;
2894 commandGlyph->disabled = true;
2895 }
2896 }
2897
2898 if ( FollowerMenu[player].optionPrevious == -1 )
2899 {
2900 repeatImg->disabled = true;
2901 repeatGlyph->disabled = true;
2902 }
2903 else
2904 {
2905 if ( auto imgGet = Image::get(repeatImg->path.c_str()) )
2906 {
2907 repeatImg->disabled = false;
2908 repeatImg->pos.w = imgGet->getWidth();
2909 repeatImg->pos.h = imgGet->getHeight();
2910 if ( commandImg->disabled )
2911 {
2912 repeatImg->pos.x = commandImg->pos.x - repeatImg->pos.w + *followerMenuRepeatGlyphX;
2913 }
2914 else
2915 {
2916 repeatImg->pos.x = commandGlyph->pos.x - repeatImg->pos.w + *followerMenuRepeatGlyphX;
2917 }
2918 repeatImg->pos.y = commandImg->pos.y + commandImg->pos.h / 2 - repeatImg->pos.h / 2 + *followerMenuRepeatGlyphY;
2919 }
2920 repeatGlyph->path = Input::inputs[player].getGlyphPathForBinding("Command NPC");
2921 if ( auto imgGet = Image::get(repeatGlyph->path.c_str()) )
2922 {
2923 repeatGlyph->disabled = repeatImg->disabled || followerDisplay.bCommandNPCDisabled;
2924 repeatGlyph->pos.w = imgGet->getWidth();
2925 repeatGlyph->pos.h = imgGet->getHeight();
2926 repeatGlyph->pos.x = repeatImg->pos.x - repeatGlyph->pos.w;
2927 repeatGlyph->pos.y = repeatImg->pos.y + repeatImg->pos.h / 2 - repeatGlyph->pos.h / 2;
2928 }
2929 else
2930 {
2931 repeatImg->disabled = true;
2932 repeatGlyph->disabled = true;
2933 }
2934 }
2935 }
2936}
2937
2938void updateAllyFollowerFrame(const int player)
2939{
2940 if ( !players[player]->hud.allyFollowerFrame )
2941 {
2942 return;
2943 }
2944
2945 auto& hud_t = players[player]->hud;
2946 Frame* baseFrame = hud_t.allyFollowerFrame;
2947 Frame* titleFrame = hud_t.allyFollowerTitleFrame;
2948 Frame* glyphFrame = hud_t.allyFollowerGlyphFrame;
2949
2950 static ConsoleVariable<bool> cvar_followerbars("/followerbars", true);
2951
2952 if ( !players[player]->isLocalPlayer() || !(*cvar_followerbars) )
2953 {
2954 baseFrame->setDisabled(true);
2955 titleFrame->setDisabled(true);
2956 glyphFrame->setDisabled(true);
2957 return;
2958 }
2959
2960 if ( !players[player]->shootmode && !FollowerMenu[player].followerMenuIsOpen()
2961 && !CalloutMenu[player].calloutMenuIsOpen()
2962 && players[player]->gui_mode != GUI_MODE_NONE )
2963 {
2964 baseFrame->setOpacity(0.0);
2965 baseFrame->setInheritParentFrameOpacity(false);
2966 }
2967 else
2968 {
2969 baseFrame->setInheritParentFrameOpacity(true);
2970 baseFrame->setOpacity(baseFrame->getParent()->getOpacity());
2971 }
2972
2973 auto& infiniteScrolling = Player::HUD_t::FollowerDisplay_t::infiniteScrolling;
2974
2975 baseFrame->setDisabled(false);
2976 SDL_Rect baseFramePos = baseFrame->getSize();
2977 baseFramePos.x = hud_t.hudFrame->getSize().w - baseFramePos.w;
2978 const int baseY = baseFramePos.y = players[player]->bUseCompactGUIWidth() ? AllyStatusBarSettings_t::FollowerBars_t::entrySettings.baseYSplitscreen : AllyStatusBarSettings_t::FollowerBars_t::entrySettings.baseY;
2979 baseFramePos.y = baseY + (infiniteScrolling ? AllyStatusBarSettings_t::FollowerBars_t::entrySettings.entryHeight : 0);
2980 baseFrame->setSize(baseFramePos);
2981
2982 titleFrame->setDisabled(true);
2983 glyphFrame->setDisabled(true);
2984 if ( infiniteScrolling )
2985 {
2986 titleFrame->setSize(SDL_Rect{ baseFramePos.x, 0, baseFramePos.w, baseY + 2 * AllyStatusBarSettings_t::FollowerBars_t::entrySettings.entryHeight });
2987 titleFrame->setDisabled(false);
2988 }
2989
2990 /*if ( enableDebugKeys && keystatus[SDLK_H] )
2991 {
2992 keystatus[SDLK_H] = 0;
2993 hud_t.followerBars.push_back(std::make_pair(0, Player::HUD_t::FollowerBar_t()));
2994 }
2995
2996 if ( enableDebugKeys && keystatus[SDLK_J] )
2997 {
2998 keystatus[SDLK_J] = 0;
2999 if ( hud_t.followerBars.size() > 0 )
3000 {
3001 auto it = hud_t.followerBars.begin() + local_rng.rand() % hud_t.followerBars.size();
3002 it->second.expired = true;
3003 }
3004 }*/
3005
3006 std::vector<Uint32> sortedUids;
3007 for ( node_t* node = stats[player]->FOLLOWERS.first; node != nullptr; node = node->next )
3008 {
3009 Entity* follower = nullptr;
3010 if ( (Uint32*)node->element )
3011 {
3012 follower = uidToEntity(*((Uint32*)node->element));
3013 sortedUids.push_back(*((Uint32*)node->element));
3014 }
3015 if ( follower )
3016 {
3017 Uint32 uid = follower->getUID();
3018 auto it = std::find_if(hud_t.followerBars.begin(), hud_t.followerBars.end(),
3019 [&uid](const std::pair<Uint32, Player::HUD_t::FollowerBar_t>& bar)
3020 {
3021 return bar.first == uid && !bar.second.expired;
3022 });
3023 if ( it == hud_t.followerBars.end() )
3024 {
3025 hud_t.followerBars.push_back(std::make_pair(uid, Player::HUD_t::FollowerBar_t()));
3026 auto& bar = hud_t.followerBars.at(hud_t.followerBars.size() - 1);
3027 bar.second.animFadeScroll = 1.0;
3028 bar.second.animFadeScrollDummy = 1.0;
3029 }
3030 }
3031 }
3032
3033 int activeBars = 0;
3034 int realActiveBars = 0;
3035 bool erasedEntry = false;
3036 for ( auto it = hud_t.followerBars.begin(); it != hud_t.followerBars.end(); )
3037 {
3038 if ( it->second.dummy )
3039 {
3040 it = hud_t.followerBars.erase(it);
3041 }
3042 else if ( it->second.expired && it->second.animy >= 0.999 )
3043 {
3044 it = hud_t.followerBars.erase(it);
3045 erasedEntry = true;
3046 }
3047 else
3048 {
3049 ++activeBars;
3050 ++realActiveBars;
3051 ++it;
3052 }
3053 }
3054
3055 auto& followerDisplay = hud_t.followerDisplay;
3056 if ( !FollowerMenu[player].recentEntity )
3057 {
3058 // try find a new follower within the visible list
3059 const int kNumEntriesToShow = Player::HUD_t::FollowerDisplay_t::getNumEntriesToShow(player);
3060 if ( infiniteScrolling )
3061 {
3062 int index = 0;
3063 for ( auto& pair : hud_t.followerBars )
3064 {
3065 if ( index >= followerDisplay.scrollSetpoint && index < kNumEntriesToShow )
3066 {
3067 if ( Entity* follower = uidToEntity(pair.first) )
3068 {
3069 // check is valid follower in list
3070 if ( std::find(sortedUids.begin(), sortedUids.end(), pair.first) != sortedUids.end() )
3071 {
3072 FollowerMenu[player].recentEntity = follower;
3073 break;
3074 }
3075 }
3076 }
3077 ++index;
3078 }
3079 }
3080 else if ( hud_t.followerBars.size() > kNumEntriesToShow )
3081 {
3082 int index = 0;
3083 for ( auto& pair : hud_t.followerBars )
3084 {
3085 if ( index >= followerDisplay.scrollSetpoint
3086 && index < (followerDisplay.scrollSetpoint + kNumEntriesToShow) )
3087 {
3088 if ( Entity* follower = uidToEntity(pair.first) )
3089 {
3090 // check is valid follower in list
3091 if ( std::find(sortedUids.begin(), sortedUids.end(), pair.first) != sortedUids.end() )
3092 {
3093 FollowerMenu[player].recentEntity = follower;
3094 break;
3095 }
3096 }
3097 }
3098 ++index;
3099 }
3100 }
3101
3102 if ( !FollowerMenu[player].recentEntity ) // find the first one if nothing found before
3103 {
3104 for ( auto& pair : hud_t.followerBars )
3105 {
3106 if ( Entity* follower = uidToEntity(pair.first) )
3107 {
3108 // check is valid follower in list
3109 if ( std::find(sortedUids.begin(), sortedUids.end(), pair.first) != sortedUids.end() )
3110 {
3111 FollowerMenu[player].recentEntity = follower;
3112 break;
3113 }
3114 }
3115 }
3116 }
3117 }
3118
3119 bool selectedFollower = false;
3120 Uint32 recentFollowerUID = FollowerMenu[player].recentEntity ? FollowerMenu[player].recentEntity->getUID() : 0;
3121 {
3122 int index = 0;
3123 for ( auto& pair : hud_t.followerBars )
3124 {
3125 pair.second.selected = false;
3126 if ( FollowerMenu[player].recentEntity )
3127 {
3128 if ( !selectedFollower && recentFollowerUID != 0 && pair.first == recentFollowerUID )
3129 {
3130 pair.second.selected = true;
3131 selectedFollower = true;
3132 if ( infiniteScrolling && erasedEntry )
3133 {
3134 // one-off check on deletion, need to snap the scroll to desired location.
3135 // otherwise, causes unneccessary scrolling for nothing as index is outdated.
3136 if ( followerDisplay.scrollSetpoint > (index + 1) )
3137 {
3138 followerDisplay.scrollSetpoint = index + 1;
3139 followerDisplay.scrollAnimateX = followerDisplay.scrollSetpoint;
3140 }
3141 }
3142 }
3143 }
3144 ++index;
3145 }
3146 }
3147
3148 if ( selectedFollower && (players[player]->shootmode || FollowerMenu[player].followerMenuIsOpen()) &&
3149 list_Size(&stats[player]->FOLLOWERS) > 0 && !players[player]->bUseCompactGUIWidth() )
3150 {
3151 glyphFrame->setDisabled(false);
3152 }
3153
3154 if ( !FollowerMenu[player].recentEntity )
3155 {
3156 followerDisplay.lastUidSelected = 0;
3157 followerDisplay.animSelected = 0.0;
3158 }
3159 else
3160 {
3161 if ( followerDisplay.lastUidSelected != recentFollowerUID )
3162 {
3163 followerDisplay.animSelected = 1.0;
3164 followerDisplay.scrollTicks = ticks;
3165 }
3166 followerDisplay.lastUidSelected = recentFollowerUID;
3167 }
3168
3169 {
3170 size_t position = 0;
3171 for ( Uint32 uid : sortedUids ) // sort the displayed list to match the stats->FOLLOWERS list order
3172 {
3173 if ( position < hud_t.followerBars.size() )
3174 {
3175 auto it2 = std::find(sortedUids.begin(), sortedUids.end(), hud_t.followerBars.at(position).first);
3176 if ( it2 == sortedUids.end() )
3177 {
3178 // this bar isn't in the follower list and expired, skip position to sort
3179 ++position;
3180 }
3181 }
3182
3183 auto it = std::find_if(hud_t.followerBars.begin() + position, hud_t.followerBars.end(),
3184 [uid](std::pair<Uint32, Player::HUD_t::FollowerBar_t>& pair) {
3185 return pair.first == uid;
3186 });
3187
3188 while ( it != hud_t.followerBars.end() )
3189 {
3190 std::iter_swap(hud_t.followerBars.begin() + position, it);
3191 ++position;
3192
3193 if ( position < hud_t.followerBars.size() )
3194 {
3195 auto it2 = std::find(sortedUids.begin(), sortedUids.end(), hud_t.followerBars.at(position).first);
3196 if ( it2 == sortedUids.end() )
3197 {
3198 // this bar isn't in the follower list and expired, skip position to sort
3199 ++position;
3200 }
3201 }
3202
3203 it = std::find_if(hud_t.followerBars.begin() + position, hud_t.followerBars.end(),
3204 [uid](std::pair<Uint32, Player::HUD_t::FollowerBar_t>& pair) {
3205 return pair.first == uid;
3206 });
3207
3208 }
3209 }
3210
3211 if ( infiniteScrolling )
3212 {
3213 int barIndex = -1;
3214 size_t vecIndex = 0;
3215 while ( vecIndex < hud_t.followerBars.size() )
3216 {
3217 ++barIndex;
3218 if ( hud_t.followerBars[vecIndex].second.dummy )
3219 {
3220 break;
3221 }
3222 if ( barIndex < (followerDisplay.scrollSetpoint - 1) )
3223 {
3224 hud_t.followerBars.push_back(hud_t.followerBars[vecIndex]);
3225 hud_t.followerBars[hud_t.followerBars.size() - 1].second.dummy = true;
3226 //messagePlayer(0, MESSAGE_DEBUG, "%.2f", hud_t.followerBars[hud_t.followerBars.size() - 1].second.animFadeScrollDummy);
3227 //hud_t.followerBars[hud_t.followerBars.size() - 1].second.animFadeScroll = 0.0;
3228 ++activeBars;
3229 }
3230 ++vecIndex;
3231 }
3232 }
3233 }
3234
3235 followerDisplay.bCompact = followerDisplay.getCompactMode(player);
3236
3237 updateAllyBarFrame(player, baseFrame, activeBars, realActiveBars, false);
3238
3239 {
3240 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
3241 real_t setpointDiffX = fpsScale * 1.0 / 10.0;
3242 followerDisplay.animSelected -= setpointDiffX;
3243 followerDisplay.animSelected = std::max(0.0, followerDisplay.animSelected);
3244 }
3245
3246 const int kNumEntriesToShow = Player::HUD_t::FollowerDisplay_t::getNumEntriesToShow(player);
3247
3248 // scroll
3249 {
3250 int scrollAmount = 0;
3251 if ( infiniteScrolling )
3252 {
3253 scrollAmount = hud_t.followerBars.size();
3254 }
3255 else if ( hud_t.followerBars.size() > kNumEntriesToShow )
3256 {
3257 scrollAmount = (hud_t.followerBars.size() - kNumEntriesToShow);
3258 }
3259 int oldSetpoint = followerDisplay.scrollSetpoint;
3260 if ( scrollAmount > 0 )
3261 {
3262 int index = 0;
3263 bool foundSelected = false;
3264 for ( auto& pair : hud_t.followerBars )
3265 {
3266 if ( pair.second.selected )
3267 {
3268 foundSelected = true;
3269 if ( infiniteScrolling )
3270 {
3271 followerDisplay.scrollSetpoint = index + 1;
3272 break;
3273 }
3274
3275 if ( index < followerDisplay.scrollSetpoint )
3276 {
3277 followerDisplay.scrollSetpoint = index;
3278 }
3279 else if ( index >= (followerDisplay.scrollSetpoint + kNumEntriesToShow) )
3280 {
3281 while ( index >= (followerDisplay.scrollSetpoint + kNumEntriesToShow) )
3282 {
3283 followerDisplay.scrollSetpoint += 1;
3284 }
3285 }
3286 break;
3287 }
3288 ++index;
3289 }
3290
3291 if ( !foundSelected )
3292 {
3293 followerDisplay.scrollSetpoint = 0;
3294 followerDisplay.scrollAnimateX = followerDisplay.scrollSetpoint;
3295 }
3296 }
3297 else
3298 {
3299 followerDisplay.scrollSetpoint = 0;
3300 }
3301
3302 followerDisplay.scrollSetpoint = std::min(followerDisplay.scrollSetpoint, scrollAmount);
3303 followerDisplay.currentScrollRow = followerDisplay.scrollSetpoint;
3304
3305 if ( abs(followerDisplay.scrollSetpoint - followerDisplay.scrollAnimateX) > 0.00001 )
3306 {
3307 if ( infiniteScrolling )
3308 {
3309 if ( followerDisplay.scrollSetpoint < followerDisplay.scrollAnimateX )
3310 {
3311 realActiveBars += std::max(0, followerDisplay.scrollSetpoint - 1); // don't delete as many if setpoint > 1
3312 followerDisplay.scrollAnimateX = followerDisplay.scrollSetpoint - 1;
3313
3314 int index = 0;
3315 std::vector<real_t> oldAnimFadeScrolls;
3316 for ( auto& pair : hud_t.followerBars )
3317 {
3318 if ( index >= oldSetpoint )
3319 {
3320 oldAnimFadeScrolls.push_back(pair.second.dummy ? pair.second.animFadeScrollDummy : pair.second.animFadeScroll);
3321 }
3322 ++index;
3323 }
3324 index = 0;
3325 for ( auto& pair : hud_t.followerBars )
3326 {
3327 if ( oldAnimFadeScrolls.size() > 0 )
3328 {
3329 if ( pair.second.dummy )
3330 {
3331 pair.second.animFadeScrollDummy = oldAnimFadeScrolls[0];
3332 }
3333 else
3334 {
3335 pair.second.animFadeScroll = oldAnimFadeScrolls[0];
3336 }
3337 oldAnimFadeScrolls.erase(oldAnimFadeScrolls.begin());
3338 }
3339 ++index;
3340 }
3341
3342 for ( int i = 0; i < realActiveBars; ++i )
3343 {
3344 if ( i + (realActiveBars - 1) < baseFrame->getFrames().size() )
3345 {
3346 baseFrame->getFrames()[i]->setOpacity(baseFrame->getFrames()[i + (realActiveBars - 1)]->getOpacity());
3347 }
3348 }
3349
3350 for ( auto f : baseFrame->getFrames() )
3351 {
3352 if ( strcmp(f->getName(), "entry") || f->isToBeDeleted() )
3353 {
3354 continue;
3355 }
3356 --realActiveBars;
3357 if ( realActiveBars <= 0 )
3358 {
3359 f->removeSelf();
3360 }
3361 }
3362 }
3363 }
3364
3365 followerDisplay.isInteractable = false;
3366 const real_t fpsScale = getFPSScale(60.0);
3367 real_t setpointDiff = 0.0;
3368 const real_t scrollSpeed = 3.0;
3369 if ( followerDisplay.scrollSetpoint - followerDisplay.scrollAnimateX > 0.0 )
3370 {
3371 real_t mult = 8.0;
3372 if ( ticks - followerDisplay.scrollTicks < TICKS_PER_SECOND50 )
3373 {
3374 mult *= (ticks - followerDisplay.scrollTicks) / (real_t)(TICKS_PER_SECOND50);
3375 }
3376 mult = fmin(mult, 1.0);
3377
3378 setpointDiff = mult * fpsScale * std::max(.03, (followerDisplay.scrollSetpoint - followerDisplay.scrollAnimateX)) / scrollSpeed;
3379 }
3380 else
3381 {
3382 real_t mult = 8.0;
3383 if ( ticks - followerDisplay.scrollTicks < TICKS_PER_SECOND50 )
3384 {
3385 mult *= (ticks - followerDisplay.scrollTicks) / (real_t)(TICKS_PER_SECOND50);
3386 }
3387 mult = fmin(mult, 1.0);
3388
3389 setpointDiff = mult * fpsScale * std::min(-.03, (followerDisplay.scrollSetpoint - followerDisplay.scrollAnimateX)) / scrollSpeed;
3390 }
3391 followerDisplay.scrollAnimateX += setpointDiff;
3392 if ( setpointDiff >= 0.0 )
3393 {
3394 followerDisplay.scrollAnimateX = std::min((real_t)followerDisplay.scrollSetpoint, followerDisplay.scrollAnimateX);
3395 }
3396 else
3397 {
3398 followerDisplay.scrollAnimateX = std::max((real_t)followerDisplay.scrollSetpoint, followerDisplay.scrollAnimateX);
3399 }
3400 }
3401 else
3402 {
3403 followerDisplay.scrollAnimateX = followerDisplay.scrollSetpoint;
3404 }
3405
3406 int scrollHeight = 0;
3407 real_t scrollRemaining = followerDisplay.scrollAnimateX;
3408 bool scrollingCompleted = false;
3409 for ( auto f : baseFrame->getFrames() )
3410 {
3411 if ( strcmp(f->getName(), "entry") || f->isToBeDeleted() )
3412 {
3413 continue;
3414 }
3415
3416 if ( !scrollingCompleted )
3417 {
3418 if ( scrollRemaining < 0.0 )
3419 {
3420 SDL_Rect pos = baseFrame->getSize();
3421 int height = AllyStatusBarSettings_t::PlayerBars_t::entrySettings.entryHeight;
3422 if ( infiniteScrolling && followerDisplay.bCompact )
3423 {
3424 height = AllyStatusBarSettings_t::PlayerBars_t::entrySettings.entryCompactHeight;
3425 }
3426 pos.y += -scrollRemaining * height;
3427 baseFrame->setSize(pos);
3428 }
3429 else if ( scrollRemaining >= 1.0 )
3430 {
3431 scrollHeight += f->getSize().h;
3432 }
3433 else
3434 {
3435 scrollHeight += f->getSize().h * scrollRemaining;
3436 }
3437 scrollRemaining -= 1.0;
3438 }
3439 {
3440 // change frame size to allow text overflow
3441 SDL_Rect pos = f->getSize();
3442 pos.h += 4;
3443 f->setSize(pos);
3444 }
3445 if ( scrollRemaining <= 0.0 )
3446 {
3447 scrollingCompleted = true;
3448 }
3449 }
3450 SDL_Rect actualSize = baseFrame->getActualSize();
3451 actualSize.y = scrollHeight;
3452 baseFrame->setActualSize(actualSize);
3453 }
3454}
3455
3456void updateAllyPlayerFrame(const int player)
3457{
3458 if ( !players[player]->hud.allyPlayerFrame )
3459 {
3460 return;
3461 }
3462
3463 auto& hud_t = players[player]->hud;
3464 Frame* baseFrame = hud_t.allyPlayerFrame;
3465
3466 static ConsoleVariable<bool> cvar_playerbars("/playerbars", true);
3467 static ConsoleVariable<int> cvar_playerbars_debug("/playerbars_debug", -1);
3468
3469 if ( !players[player]->isLocalPlayer() || !(*cvar_playerbars) )
3470 {
3471 baseFrame->setDisabled(true);
3472 return;
3473 }
3474
3475 if ( !players[player]->shootmode && !FollowerMenu[player].followerMenuIsOpen()
3476 && !CalloutMenu[player].calloutMenuIsOpen()
3477 && players[player]->gui_mode != GUI_MODE_NONE )
3478 {
3479 baseFrame->setOpacity(0.0);
3480 baseFrame->setInheritParentFrameOpacity(false);
3481 }
3482 else
3483 {
3484 baseFrame->setInheritParentFrameOpacity(true);
3485 baseFrame->setOpacity(baseFrame->getParent()->getOpacity());
3486 }
3487
3488 baseFrame->setDisabled(false);
3489 SDL_Rect baseFramePos = baseFrame->getSize();
3490 baseFramePos.x = 8;
3491 baseFramePos.y = players[player]->bUseCompactGUIWidth() ? AllyStatusBarSettings_t::PlayerBars_t::entrySettings.baseYSplitscreen : AllyStatusBarSettings_t::PlayerBars_t::entrySettings.baseY;
3492 baseFrame->setSize(baseFramePos);
3493
3494 if ( *cvar_playerbars_debug >= 0 )
3495 {
3496 if ( hud_t.playerBars.size() < *cvar_playerbars_debug )
3497 {
3498 while ( hud_t.playerBars.size() < *cvar_playerbars_debug )
3499 {
3500 hud_t.playerBars.push_back(std::make_pair(0, Player::HUD_t::FollowerBar_t()));
3501 }
3502 }
3503 else if ( hud_t.playerBars.size() > *cvar_playerbars_debug )
3504 {
3505 hud_t.playerBars.clear();
3506 }
3507 }
3508 /*if ( enableDebugKeys && keystatus[SDLK_H] )
3509 {
3510 keystatus[SDLK_H] = 0;
3511 hud_t.playerBars.push_back(std::make_pair(0, Player::HUD_t::FollowerBar_t()));
3512 }
3513
3514 if ( enableDebugKeys && keystatus[SDLK_J] )
3515 {
3516 keystatus[SDLK_J] = 0;
3517 if ( hud_t.playerBars.size() > 0 )
3518 {
3519 auto it = hud_t.playerBars.begin() + local_rng.rand() % hud_t.playerBars.size();
3520 it->second.expired = true;
3521 }
3522 }*/
3523
3524 for ( int i = 0; i < MAXPLAYERS4; ++i )
3525 {
3526 if ( i == player )
3527 {
3528 continue;
3529 }
3530 if ( !client_disconnected[i] && players[i]->entity && !splitscreen )
3531 {
3532 auto it = std::find_if(hud_t.playerBars.begin(), hud_t.playerBars.end(),
3533 [&i](const std::pair<Uint32, Player::HUD_t::FollowerBar_t>& bar)
3534 {
3535 return bar.first == i && !bar.second.expired;
3536 });
3537 if ( it == hud_t.playerBars.end() )
3538 {
3539 hud_t.playerBars.push_back(std::make_pair(i, Player::HUD_t::FollowerBar_t()));
3540 auto& bar = hud_t.playerBars.at(hud_t.playerBars.size() - 1);
3541 bar.second.animFadeScroll = 1.0;
3542 bar.second.animFadeScrollDummy = 1.0;
3543 }
3544 }
3545 }
3546
3547 int activeBars = 0;
3548 for ( auto it = hud_t.playerBars.begin(); it != hud_t.playerBars.end(); )
3549 {
3550 int playernum = it->first;
3551 if ( playernum >= 0 && playernum < MAXPLAYERS4
3552 && client_disconnected[playernum] )
3553 {
3554 it = hud_t.playerBars.erase(it);
3555 }
3556 else if ( it->second.expired && it->second.animy >= 0.999 )
3557 {
3558 it = hud_t.playerBars.erase(it);
3559 }
3560 else
3561 {
3562 ++activeBars;
3563 ++it;
3564 }
3565 }
3566
3567 updateAllyBarFrame(player, baseFrame, activeBars, activeBars, true);
3568}
3569
3570void createEnemyBar(const int player, Frame*& frame)
3571{
3572 auto& hud_t = players[player]->hud;
3573 frame = hud_t.hudFrame->addFrame("enemy bar");
3574 frame->setHollow(true);
3575 frame->setInheritParentFrameOpacity(false);
3576 frame->setOpacity(0.0);
3577 const int barTotalHeight = hud_t.ENEMYBAR_FRAME_HEIGHT;
3578 const int barStartY = (hud_t.hudFrame->getSize().h - hud_t.ENEMYBAR_FRAME_START_Y - 100);
3579 const int barWidth = hud_t.ENEMYBAR_FRAME_WIDTH;
3580
3581 SDL_Rect pos{ (hud_t.hudFrame->getSize().w / 2) - barWidth / 2 - 6, barStartY, barWidth, barTotalHeight };
3582 frame->setSize(pos);
3583
3584 auto bg = frame->addImage(pos, 0xFFFFFFFF, "*#images/ui/HUD/enemybar/HUD_EnemyHP_Back_Body_01.png", "base img");
3585 bg->pos.x = 6;
3586 bg->pos.h = 34;
3587 bg->pos.y = 4;
3588 bg->pos.w = 548;
3589 bg->color = makeColor(255, 255, 255, 255);
3590
3591 auto bgEndCap = frame->addImage(pos, 0xFFFFFFFF, "*#images/ui/HUD/enemybar/HUD_EnemyHP_Back_Cap_01.png", "base img endcap");
3592 bgEndCap->pos.x = bg->pos.x + bg->pos.w;
3593 bgEndCap->pos.h = bg->pos.h;
3594 bgEndCap->pos.y = bg->pos.y;
3595 bgEndCap->pos.w = 8;
3596 bgEndCap->color = bg->color;
3597
3598 auto dmgFrame = frame->addFrame("bar dmg frame");
3599 dmgFrame->setSize(SDL_Rect{ bg->pos.x, bg->pos.y, bg->pos.w + bgEndCap->pos.w, 34 });
3600 dmgFrame->setInheritParentFrameOpacity(false);
3601 auto dmg = dmgFrame->addImage(pos, 0xFFFFFFFF, "*#images/ui/HUD/enemybar/HUD_EnemyHP_DMG_Body_01.png", "dmg img");
3602 dmg->pos.x = 0;
3603 dmg->pos.h = bg->pos.h;
3604 dmg->pos.y = 0;
3605 dmg->pos.w = 548 / 2 + 100;
3606 dmg->color = makeColor(255, 255, 255, 255);
3607
3608 auto dmgEndCap = dmgFrame->addImage(pos, 0xFFFFFFFF, "*#images/ui/HUD/enemybar/HUD_EnemyHP_DMG_Cap_01.png", "dmg img endcap");
3609 dmgEndCap->pos.x = dmg->pos.w;
3610 dmgEndCap->pos.h = dmg->pos.h;
3611 dmgEndCap->pos.y = 0;
3612 dmgEndCap->pos.w = 10;
3613 dmgEndCap->color = dmg->color;
3614
3615 /*auto bubbles = dmgFrame->addImage(pos, 0xFFFFFFFF, "*#images/ui/HUD/enemybar/HUD_EnemyHP_Bubbles_00.png", "img bubbles");
3616 bubbles->pos.x = 0;
3617 bubbles->pos.h = 20;
3618 bubbles->pos.y = dmgFrame->getSize().h / 2 - bubbles->pos.h / 2;
3619 if ( auto img = Image::get(bubbles->path.c_str()) )
3620 {
3621 bubbles->pos.w = img->getWidth();
3622 }*/
3623
3624 auto progressFrame = frame->addFrame("bar progress frame");
3625 progressFrame->setSize(SDL_Rect{ bg->pos.x, bg->pos.y + 2, bg->pos.w + bgEndCap->pos.w, 30 });
3626 progressFrame->setOpacity(100.0);
3627 auto fg = progressFrame->addImage(pos, 0xFFFFFFFF, "*#images/ui/HUD/enemybar/HUD_EnemyHP_Fill_Body_00.png", "progress img");
3628 fg->pos.x = 0;
3629 fg->pos.h = bg->pos.h - 4;
3630 fg->pos.y = 0;
3631 fg->pos.w = 548;
3632
3633 auto fgEndCap = progressFrame->addImage(pos, 0xFFFFFFFF, "*#images/ui/HUD/enemybar/HUD_EnemyHP_Fill_Cap_00.png", "progress img endcap");
3634 fgEndCap->pos.x = fg->pos.x + fg->pos.w;
3635 fgEndCap->pos.h = fg->pos.h;
3636 fgEndCap->pos.y = 0;
3637 if ( auto img = Image::get(fgEndCap->path.c_str()) )
3638 {
3639 fgEndCap->pos.w = img->getWidth();
3640 }
3641
3642 auto skullFrame = frame->addFrame("skull frame");
3643 skullFrame->setSize(SDL_Rect{ 0, 0, 24, 44 });
3644 auto skull = skullFrame->addImage(skullFrame->getSize(), 0xFFFFFFFF, "*#images/ui/HUD/enemybar/HUD_EnemyHP_Face4_00.png", "skull 0 img");
3645 skull = skullFrame->addImage(skullFrame->getSize(), 0xFFFFFFFF, "*#images/ui/HUD/enemybar/HUD_EnemyHP_Face3_00.png", "skull 25 img");
3646 skull = skullFrame->addImage(skullFrame->getSize(), 0xFFFFFFFF, "*#images/ui/HUD/enemybar/HUD_EnemyHP_Face2_00.png", "skull 50 img");
3647 skull = skullFrame->addImage(skullFrame->getSize(), 0xFFFFFFFF, "*#images/ui/HUD/enemybar/HUD_EnemyHP_Face1_00.png", "skull 100 img");
3648
3649 std::string font = "fonts/pixel_maz.ttf#32#2";
3650 Uint32 color = makeColor(235, 191, 140, 255);
3651 auto enemyName = frame->addField("enemy name txt", 128);
3652 enemyName->setSize(SDL_Rect{ 0, 0, frame->getSize().w, frame->getSize().h});
3653 enemyName->setFont(font.c_str());
3654 enemyName->setColor(color);
3655 enemyName->setHJustify(Field::justify_t::CENTER);
3656 enemyName->setVJustify(Field::justify_t::CENTER);
3657 enemyName->setText("");
3658 enemyName->setOntop(true);
3659
3660 auto dmgText = hud_t.hudFrame->addField("enemy dmg txt", 128);
3661 dmgText->setSize(SDL_Rect{ 0, 0, 0, 0 });
3662 dmgText->setFont("fonts/pixel_maz.ttf#32#2");
3663 dmgText->setColor(color);
3664 dmgText->setHJustify(Field::justify_t::LEFT);
3665 dmgText->setVJustify(Field::justify_t::TOP);
3666 dmgText->setText("0");
3667 dmgText->setOntop(true);
3668}
3669
3670std::vector<std::vector<std::string>> playerXPCapPaths = {
3671 {
3672 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_00.png",
3673 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_00a.png",
3674 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_00b.png",
3675 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_00c.png",
3676 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_00d.png",
3677 },
3678 {
3679 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_01.png",
3680 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_01a.png",
3681 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_01b.png",
3682 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_01c.png",
3683 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_01d.png",
3684 },
3685 {
3686 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_02.png",
3687 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_02a.png",
3688 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_02b.png",
3689 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_02c.png",
3690 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_02d.png",
3691 },
3692 {
3693 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_03.png",
3694 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_03a.png",
3695 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_03b.png",
3696 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_03c.png",
3697 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_03d.png",
3698 },
3699 {
3700 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_04.png",
3701 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_04a.png",
3702 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_04b.png",
3703 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_04c.png",
3704 "*#images/ui/HUD/xpbar/HUD_Exp_SandCap_04d.png",
3705 }
3706};
3707
3708void createXPBar(const int player)
3709{
3710 auto& hud_t = players[player]->hud;
3711 hud_t.xpFrame = hud_t.hudFrame->addFrame("xp bar");
3712 hud_t.xpFrame->setHollow(true);
3713
3714 const int xpBarStartY = (hud_t.hudFrame->getSize().h) - hud_t.XP_FRAME_START_Y;
3715 const int xpBarWidth = hud_t.XP_FRAME_WIDTH;
3716 const int xpBarTotalHeight = hud_t.XP_FRAME_HEIGHT;
3717 SDL_Rect pos { (hud_t.hudFrame->getSize().w / 2) - xpBarWidth / 2, xpBarStartY, xpBarWidth, xpBarTotalHeight };
3718 hud_t.xpFrame->setSize(pos);
3719
3720 //auto bg = hud_t.xpFrame->addImage(pos, 0xFFFFFFFF, "*#images/ui/HUD/xpbar/HUD_Bars_Base_00.png", "xp img base");
3721 auto bg = hud_t.xpFrame->addImage(pos, 0xFFFFFFFF, "*#images/ui/HUD/xpbar/HUD_Exp_Surround_01.png", "xp img base");
3722 bg->pos.x = 0;
3723 bg->pos.h = 26;
3724 bg->pos.y = 4;
3725 auto bgFlair = hud_t.xpFrame->addImage(SDL_Rect{4, 10, 0, 14}, makeColor(255, 255, 255, 192), "*#images/ui/HUD/xpbar/HUD_Exp_GlassShine_03.png", "xp img base flair");
3726
3727 // xpProgress only adjusts width
3728 const int progressBarHeight = 22;
3729 /*auto xpProgress = hud_t.xpFrame->addImage(SDL_Rect{ 0, 6, 1, progressBarHeight }, 0xFFFFFFFF,
3730 "*#images/ui/HUD/xpbar/HUD_Bars_ExpMid_00.png", "xp img progress");*/
3731 auto progressClipFrame = hud_t.xpFrame->addFrame("xp progress clipping frame");
3732 progressClipFrame->setSize(SDL_Rect{ 0, 6, 1, progressBarHeight });
3733
3734 std::string bodyPath = "*#images/ui/HUD/xpbar/HUD_Exp_SandBody2_";
3735 int xpPathNum = player;
3736 if ( !colorblind_lobby )
3737 {
3738 switch ( player )
3739 {
3740 case 0:
3741 default:
3742 bodyPath += "00.png";
3743 break;
3744 case 1:
3745 bodyPath += "01.png";
3746 break;
3747 case 2:
3748 bodyPath += "02.png";
3749 break;
3750 case 3:
3751 bodyPath += "03.png";
3752 break;
3753 }
3754 }
3755 else
3756 {
3757 switch ( player )
3758 {
3759 case 0:
3760 default:
3761 bodyPath += "02.png";
3762 xpPathNum = 2;
3763 break;
3764 case 1:
3765 bodyPath += "03.png";
3766 xpPathNum = 3;
3767 break;
3768 case 2:
3769 bodyPath += "01.png";
3770 xpPathNum = 1;
3771 break;
3772 case 3:
3773 bodyPath += "04.png";
3774 xpPathNum = 4;
3775 break;
3776 }
3777 }
3778 if ( player >= playerXPCapPaths.size() )
3779 {
3780 xpPathNum = 0;
3781 }
3782
3783 auto progressClipImg = progressClipFrame->addImage(SDL_Rect{ 0, 0, 634, progressBarHeight }, 0xFFFFFFFF,
3784 bodyPath.c_str(), "xp img progress clipped");
3785
3786 auto xpProgress = hud_t.xpFrame->addImage(SDL_Rect{ 0, 6, 1, progressBarHeight }, 0xFFFFFFFF,
3787 bodyPath.c_str(), "xp img progress");
3788
3789 // xpProgressEndCap only adjusts x position based on xpProgress->pos.x + xpProgress->pos.w
3790 /*auto xpProgressEndCap = hud_t.xpFrame->addImage(SDL_Rect{0, 6, 8, progressBarHeight }, 0xFFFFFFFF,
3791 "*#images/ui/HUD/xpbar/HUD_Bars_ExpEnd_00.png", "xp img progress endcap");*/
3792
3793 auto xpProgressEndCap = hud_t.xpFrame->addImage(SDL_Rect{ 0, 6, 38, progressBarHeight }, 0xFFFFFFFF,
3794 playerXPCapPaths[xpPathNum][0].c_str(), "xp img progress endcap");
3795
3796
3797 const int endCapWidth = 26;
3798 SDL_Rect endCapPos {0, 0, endCapWidth, xpBarTotalHeight};
3799 auto endCapLeft = hud_t.xpFrame->addImage(endCapPos, 0xFFFFFFFF, "*#images/ui/HUD/xpbar/HUD_Bars_ExpCap1_00.png", "xp img endcap left");
3800 endCapLeft->ontop = true;
3801 endCapPos.x = pos.w - endCapPos.w;
3802 auto endCapRight = hud_t.xpFrame->addImage(endCapPos, 0xFFFFFFFF, "*#images/ui/HUD/xpbar/HUD_Bars_ExpCap2_00.png", "xp img endcap right");
3803 endCapRight->ontop = true;
3804
3805 const int textWidth = 72;
3806 auto font = "fonts/pixel_maz.ttf#32#2";
3807 auto textStatic = hud_t.xpFrame->addField("xp text static", 16);
3808 textStatic->setText(Language::get(6106));
3809 textStatic->setOntop(true);
3810 textStatic->setSize(SDL_Rect{ pos.w / 2 - 4, 0, textWidth, pos.h }); // x - 4 to center the slash
3811 textStatic->setFont(font);
3812 textStatic->setVJustify(Field::justify_t::CENTER);
3813 textStatic->setHJustify(Field::justify_t::LEFT);
3814 textStatic->setColor(makeColor( 255, 255, 255, 255));
3815
3816 auto text = hud_t.xpFrame->addField("xp text current", 16);
3817 text->setText("0");
3818 text->setOntop(true);
3819 text->setSize(SDL_Rect{ pos.w / 2 - (4 * 2) - textWidth, 0, textWidth, pos.h }); // x - 4 to center the slash
3820 text->setFont(font);
3821 text->setVJustify(Field::justify_t::CENTER);
3822 text->setHJustify(Field::justify_t::RIGHT);
3823 text->setColor(makeColor( 255, 255, 255, 255));
3824
3825 auto textLevel = hud_t.xpFrame->addField("xp text lvl", 64);
3826 textLevel->setText("");
3827 textLevel->setOntop(true);
3828 textLevel->setDisabled(true);
3829 textLevel->setSize(SDL_Rect{ 0, 0, 0, pos.h }); // x - 4 to center the slash
3830 textLevel->setFont(font);
3831 textLevel->setVJustify(Field::justify_t::CENTER);
3832 textLevel->setHJustify(Field::justify_t::LEFT);
3833 textLevel->setColor(makeColor(255, 255, 255, 255));
3834
3835 auto textClass = hud_t.xpFrame->addField("xp text class", 64);
3836 textClass->setText("");
3837 textClass->setOntop(true);
3838 textClass->setDisabled(true);
3839 textClass->setSize(SDL_Rect{ 0, 0, 0, pos.h }); // x - 4 to center the slash
3840 textClass->setFont(font);
3841 textClass->setVJustify(Field::justify_t::CENTER);
3842 textClass->setHJustify(Field::justify_t::LEFT);
3843 textClass->setColor(makeColor(255, 255, 255, 255));
3844}
3845
3846void createHotbar(const int player)
3847{
3848 auto& hotbar_t = players[player]->hotbar;
3849 if ( !hotbar_t.hotbarFrame )
3850 {
3851 return;
3852 }
3853 Uint32 color = makeColor( 255, 255, 255, hotbarSlotOpacity);
3854 SDL_Rect slotPos{ 0, 0, hotbar_t.getSlotSize(), hotbar_t.getSlotSize() };
3855 std::array<int, NUM_HOTBAR_SLOTS> slotCreationOrder = {
3856 0, 2, 3, 5, 6, 8, 9, 1, 4, 7
3857 };
3858 for ( auto& i : slotCreationOrder )
3859 {
3860 char slotname[32];
3861 snprintf(slotname, sizeof(slotname), "hotbar slot %d", i);
3862 auto slot = hotbar_t.hotbarFrame->addFrame(slotname);
3863 slot->setSize(slotPos);
3864 slot->addImage(slotPos, color, "*#images/ui/HUD/hotbar/HUD_Quickbar_Slot_Box_02.png", "slot img");
3865 hotbar_t.hotbarSlotFrames[i] = slot;
3866
3867 char glyphname[32];
3868 snprintf(glyphname, sizeof(glyphname), "hotbar glyph %d", i);
3869 auto path = Input::getGlyphPathForInput("ButtonA", false, Input::getControllerType(player));
3870 auto glyph = hotbar_t.hotbarFrame->addImage(slotPos, 0xFFFFFFFF, path.c_str(), glyphname);
3871 glyph->disabled = true;
3872 }
3873
3874 auto font = "fonts/pixel_maz.ttf#32#2";
3875
3876 for ( int i = 0; i < NUM_HOTBAR_SLOTS; ++i )
3877 {
3878 auto slot = hotbar_t.getHotbarSlotFrame(i);
3879 assert(slot)(static_cast<void> (0));
3880
3881 auto itemSlot = slot->addFrame("hotbar slot item");
3882 SDL_Rect itemSlotTempSize = slot->getSize();
3883 itemSlotTempSize.w -= 4;
3884 itemSlotTempSize.h -= 4;
3885 itemSlot->setSize(itemSlotTempSize); // slightly shrink to align inner item elements within rect.
3886 createPlayerInventorySlotFrameElements(itemSlot);
3887 itemSlot->setSize(slot->getSize());
3888
3889 char numStr[32];
3890 if ( i + 1 == 10 )
3891 {
3892 snprintf(numStr, sizeof(numStr), "%d", 0);
3893 }
3894 else
3895 {
3896 snprintf(numStr, sizeof(numStr), "%d", i + 1);
3897 }
3898 auto text = slot->addField("slot num text", 32);
3899 text->setText(numStr);
3900 text->setSize(SDL_Rect{ 0, -4, slotPos.w, slotPos.h });
3901 text->setFont(font);
3902 text->setVJustify(Field::justify_t::TOP);
3903 text->setHJustify(Field::justify_t::LEFT);
3904 text->setOntop(true);
3905 }
3906
3907 auto highlightFrame = hotbar_t.hotbarFrame->addFrame("hotbar highlight");
3908 highlightFrame->setSize(slotPos);
3909 highlightFrame->addImage(slotPos, color, "*#images/ui/HUD/hotbar/HUD_Quickbar_Slot_HighlightBox_02.png", "highlight img");
3910
3911 auto itemSlot = highlightFrame->addFrame("hotbar slot item");
3912 SDL_Rect itemSlotTempSize = slotPos;
3913 itemSlotTempSize.w -= 4;
3914 itemSlotTempSize.h -= 4;
3915 itemSlot->setSize(itemSlotTempSize);
3916 createPlayerInventorySlotFrameElements(itemSlot); // slightly shrink to align inner item elements within rect.
3917 itemSlot->setSize(highlightFrame->getSize());
3918
3919 {
3920 auto oldSelectedFrame = hotbar_t.hotbarFrame->addFrame("hotbar old selected item");
3921 SDL_Rect oldSelectedFramePos = slotPos;
3922 oldSelectedFramePos.w -= 2;
3923 oldSelectedFramePos.h -= 2;
3924 oldSelectedFrame->setSize(oldSelectedFramePos);
3925 oldSelectedFrame->setDisabled(true);
3926
3927 const int itemSpriteSize = players[oldSelectedFrame->getOwner()]->inventoryUI.getItemSpriteSize();
3928 SDL_Rect itemSpriteBorder{ 5, 5, itemSpriteSize, itemSpriteSize };
3929
3930 color = makeColor( 0, 255, 255, 255);
3931 auto oldImg = oldSelectedFrame->addImage(itemSpriteBorder,
3932 makeColor( 255, 255, 255, 128), "", "hotbar old selected item");
3933 oldImg->disabled = true;
3934 oldSelectedFrame->addImage(SDL_Rect{ 0, 0, oldSelectedFrame->getSize().w, oldSelectedFrame->getSize().h },
3935 color, "*images/system/hotbar_slot.png", "hotbar old selected highlight");
3936
3937 auto oldCursorFrame = hotbar_t.hotbarFrame->addFrame("hotbar old item cursor");
3938 oldCursorFrame->setSize(SDL_Rect{ 0, 0, oldSelectedFramePos.w + 16, oldSelectedFramePos.h + 16 });
3939 oldCursorFrame->setDisabled(true);
3940 color = makeColor( 255, 255, 255, oldSelectedCursorOpacity);
3941 oldCursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
3942 color, "*#images/ui/Inventory/SelectorGrey_TL.png", "hotbar old cursor topleft");
3943 oldCursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
3944 color, "*#images/ui/Inventory/SelectorGrey_TR.png", "hotbar old cursor topright");
3945 oldCursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
3946 color, "*#images/ui/Inventory/SelectorGrey_BL.png", "hotbar old cursor bottomleft");
3947 oldCursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
3948 color, "*#images/ui/Inventory/SelectorGrey_BR.png", "hotbar old cursor bottomright");
3949
3950 auto cursorFrame = hotbar_t.hotbarFrame->addFrame("shootmode selected item cursor");
3951 cursorFrame->setSize(SDL_Rect{ 0, 0, oldSelectedFramePos.w + 16, oldSelectedFramePos.h + 16 });
3952 cursorFrame->setDisabled(true);
3953 color = makeColor( 255, 255, 255, selectedCursorOpacity);
3954 cursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
3955 color, "*#images/ui/Inventory/Selector_TL.png", "shootmode selected cursor topleft");
3956 cursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
3957 color, "*#images/ui/Inventory/Selector_TR.png", "shootmode selected cursor topright");
3958 cursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
3959 color, "*#images/ui/Inventory/Selector_BL.png", "shootmode selected cursor bottomleft");
3960 cursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
3961 color, "*#images/ui/Inventory/Selector_BR.png", "shootmode selected cursor bottomright");
3962 }
3963
3964 auto cancelPromptTxt = hotbar_t.hotbarFrame->addField("hotbar cancel prompt", 32);
3965 cancelPromptTxt->setText(Language::get(3063));
3966 cancelPromptTxt->setSize(SDL_Rect{ 0, 0, 100, 24 });
3967 cancelPromptTxt->setDisabled(true);
3968 cancelPromptTxt->setFont(font);
3969 cancelPromptTxt->setVJustify(Field::justify_t::TOP);
3970 cancelPromptTxt->setHJustify(Field::justify_t::LEFT);
3971 auto cancelPromptGlyph = hotbar_t.hotbarFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
3972 0xFFFFFFFF, "", "hotbar cancel glyph");
3973 cancelPromptGlyph->disabled = true;
3974
3975 auto text = highlightFrame->addField("slot num text", 32);
3976 text->setText("");
3977 text->setSize(SDL_Rect{ 0, -4, slotPos.w, slotPos.h });
3978 text->setFont(font);
3979 text->setVJustify(Field::justify_t::TOP);
3980 text->setHJustify(Field::justify_t::LEFT);
3981 text->setOntop(true);
3982}
3983
3984void createUINavigation(const int player)
3985{
3986 auto& hud_t = players[player]->hud;
3987 auto& uiNavFrame = hud_t.uiNavFrame;
3988 uiNavFrame = hud_t.hudFrame->addFrame("ui navigation");
3989 uiNavFrame->setHollow(true);
3990 uiNavFrame->setBorder(0);
3991 uiNavFrame->setOwner(player);
3992 uiNavFrame->setSize(SDL_Rect{ 0, 0, hud_t.hudFrame->getSize().w, hud_t.hudFrame->getSize().h });
3993 uiNavFrame->setDisabled(true);
3994 {
3995 const int glyphSize = 32;
3996 const char* buttonFont = "fonts/pixel_maz.ttf#32#2";
3997 auto magicButton = uiNavFrame->addButton("magic button");
3998 magicButton->setText(Language::get(4115));
3999 magicButton->setFont(buttonFont);
4000 magicButton->setBackground("*#images/ui/HUD/HUD_Button_Base_Small_00.png");
4001 magicButton->setBackgroundActivated("*#images/ui/HUD/HUD_Button_Base_SmallPress_00.png");
4002 magicButton->setBackgroundHighlighted("*#images/ui/HUD/HUD_Button_Base_SmallHigh_00.png");
4003 magicButton->setSize(SDL_Rect{ 0, 0, 98, 38 });
4004 magicButton->setHideGlyphs(true);
4005 magicButton->setHideKeyboardGlyphs(true);
4006 magicButton->setHideSelectors(true);
4007 magicButton->setMenuConfirmControlType(0);
4008 magicButton->setColor(makeColor(255, 255, 255, 255));
4009 magicButton->setHighlightColor(makeColor(255, 255, 255, 255));
4010 magicButton->setCallback([](Button& button) {
4011 if ( inputs.getVirtualMouse(button.getOwner())->draw_cursor )
4012 {
4013 // prevent 1 frame flickering of hud.cursor after click
4014 players[button.getOwner()]->GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_INVENTORY);
4015 }
4016 if ( players[button.getOwner()]->inventory_mode == INVENTORY_MODE_ITEM )
4017 {
4018 players[button.getOwner()]->GUI.activateModule(Player::GUI_t::MODULE_INVENTORY);
4019 players[button.getOwner()]->gui_mode = GUI_MODE_INVENTORY;
4020 players[button.getOwner()]->inventoryUI.cycleInventoryTab();
4021 players[button.getOwner()]->inventoryUI.spellPanel.openSpellPanel();
4022 }
4023 else if ( players[button.getOwner()]->inventory_mode == INVENTORY_MODE_SPELL )
4024 {
4025 players[button.getOwner()]->GUI.activateModule(Player::GUI_t::MODULE_SPELLS);
4026 players[button.getOwner()]->gui_mode = GUI_MODE_INVENTORY;
4027 players[button.getOwner()]->inventoryUI.cycleInventoryTab();
4028 players[button.getOwner()]->inventoryUI.spellPanel.closeSpellPanel();
4029 }
4030 });
4031 auto magicButtonGlyph = uiNavFrame->addImage(SDL_Rect{ 0, 0, glyphSize, glyphSize },
4032 0xFFFFFFFF, "images/system/white.png", "magic button glyph")->disabled = true;
4033
4034 auto statusButton = uiNavFrame->addButton("status button");
4035 statusButton->setText(Language::get(4118));
4036 statusButton->setFont(buttonFont);
4037 statusButton->setBackground("*#images/ui/HUD/HUD_Button_Base_Small_00.png");
4038 statusButton->setBackgroundActivated("*#images/ui/HUD/HUD_Button_Base_SmallPress_00.png");
4039 statusButton->setBackgroundHighlighted("*#images/ui/HUD/HUD_Button_Base_SmallHigh_00.png");
4040 statusButton->setSize(SDL_Rect{ 0, 0, 98, 38 });
4041 statusButton->setHideGlyphs(true);
4042 statusButton->setHideKeyboardGlyphs(true);
4043 statusButton->setHideSelectors(true);
4044 statusButton->setMenuConfirmControlType(0);
4045 statusButton->setColor(makeColor(255, 255, 255, 255));
4046 statusButton->setHighlightColor(makeColor(255, 255, 255, 255));
4047 statusButton->setCallback([](Button& button) {
4048 Player::soundActivate();
4049 if ( players[button.getOwner()]->hud.compactLayoutMode != Player::HUD_t::COMPACT_LAYOUT_CHARSHEET )
4050 {
4051 players[button.getOwner()]->inventoryUI.slideOutPercent = 1.0;
4052 }
4053 players[button.getOwner()]->hud.compactLayoutMode = Player::HUD_t::COMPACT_LAYOUT_CHARSHEET;
4054 if ( inputs.getVirtualMouse(button.getOwner())->draw_cursor )
4055 {
4056 // prevent 1 frame flickering of hud.cursor after click
4057 players[button.getOwner()]->GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_INVENTORY);
4058 }
4059 players[button.getOwner()]->GUI.activateModule(Player::GUI_t::MODULE_CHARACTERSHEET);
4060 if ( players[button.getOwner()]->characterSheet.selectedElement == Player::CharacterSheet_t::SHEET_UNSELECTED )
4061 {
4062 players[button.getOwner()]->characterSheet.selectElement(Player::CharacterSheet_t::SHEET_OPEN_MAP, false, false);
4063 }
4064 players[button.getOwner()]->GUI.warpControllerToModule(false);
4065 });
4066 auto statusButtonGlyph = uiNavFrame->addImage(SDL_Rect{ 0, 0, glyphSize, glyphSize },
4067 0xFFFFFFFF, "images/system/white.png", "status button glyph")->disabled = true;
4068
4069 auto itemsButton = uiNavFrame->addButton("items button");
4070 itemsButton->setText(Language::get(4116));
4071 itemsButton->setFont(buttonFont);
4072 itemsButton->setBackground("*#images/ui/HUD/HUD_Button_Base_Small_00.png");
4073 itemsButton->setBackgroundActivated("*#images/ui/HUD/HUD_Button_Base_SmallPress_00.png");
4074 itemsButton->setBackgroundHighlighted("*#images/ui/HUD/HUD_Button_Base_SmallHigh_00.png");
4075 itemsButton->setSize(SDL_Rect{ 0, 0, 98, 38 });
4076 itemsButton->setHideGlyphs(true);
4077 itemsButton->setHideKeyboardGlyphs(true);
4078 itemsButton->setHideSelectors(true);
4079 itemsButton->setMenuConfirmControlType(0);
4080 itemsButton->setColor(makeColor(255, 255, 255, 255));
4081 itemsButton->setHighlightColor(makeColor(255, 255, 255, 255));
4082 itemsButton->setCallback([](Button& button) {
4083 if ( players[button.getOwner()]->hud.compactLayoutMode != Player::HUD_t::COMPACT_LAYOUT_INVENTORY )
4084 {
4085 Player::soundActivate();
4086 }
4087 players[button.getOwner()]->hud.compactLayoutMode = Player::HUD_t::COMPACT_LAYOUT_INVENTORY;
4088 if ( inputs.getVirtualMouse(button.getOwner())->draw_cursor )
4089 {
4090 // prevent 1 frame flickering of hud.cursor after click
4091 players[button.getOwner()]->GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_INVENTORY);
4092 }
4093 if ( players[button.getOwner()]->inventory_mode == INVENTORY_MODE_SPELL
4094 && players[button.getOwner()]->GUI.activeModule == Player::GUI_t::MODULE_SPELLS )
4095 {
4096 players[button.getOwner()]->inventoryUI.cycleInventoryTab();
4097 }
4098 else
4099 {
4100 players[button.getOwner()]->openStatusScreen(GUI_MODE_INVENTORY, INVENTORY_MODE_ITEM, Player::GUI_t::MODULE_INVENTORY);
4101 }
4102 });
4103
4104 auto itemsButtonGlyph = uiNavFrame->addImage(SDL_Rect{ 0, 0, glyphSize, glyphSize },
4105 0xFFFFFFFF, "images/system/white.png", "items button glyph")->disabled = true;
4106
4107 auto skillsButton = uiNavFrame->addButton("skills button");
4108 skillsButton->setText(Language::get(4117));
4109 skillsButton->setFont(buttonFont);
4110 skillsButton->setBackground("*#images/ui/HUD/HUD_Button_Base_Small_00.png");
4111 skillsButton->setBackgroundActivated("*#images/ui/HUD/HUD_Button_Base_SmallPress_00.png");
4112 skillsButton->setBackgroundHighlighted("*#images/ui/HUD/HUD_Button_Base_SmallHigh_00.png");
4113 skillsButton->setSize(SDL_Rect{ 0, 0, 98, 38 });
4114 skillsButton->setHideGlyphs(true);
4115 skillsButton->setHideKeyboardGlyphs(true);
4116 skillsButton->setHideSelectors(true);
4117 skillsButton->setMenuConfirmControlType(0);
4118 skillsButton->setColor(makeColor(255, 255, 255, 255));
4119 skillsButton->setHighlightColor(makeColor(255, 255, 255, 255));
4120 skillsButton->setCallback([](Button& button) {
4121 players[button.getOwner()]->skillSheet.openSkillSheet();
4122 });
4123
4124 auto skillsButtonGlyph = uiNavFrame->addImage(SDL_Rect{ 0, 0, glyphSize, glyphSize },
4125 0xFFFFFFFF, "images/system/white.png", "skills button glyph")->disabled = true;
4126 }
4127 {
4128 const int glyphSize = 32;
4129 const char* navFont = "fonts/pixel_maz.ttf#32#2";
4130 auto leftBumperNavigationTxt = uiNavFrame->addField("left bumper txt", 64);
4131 leftBumperNavigationTxt->setFont(navFont);
4132 leftBumperNavigationTxt->setHJustify(Field::justify_t::RIGHT);
4133 leftBumperNavigationTxt->setVJustify(Field::justify_t::CENTER);
4134 leftBumperNavigationTxt->setDisabled(true);
4135
4136 auto leftBumperNavigationImg = uiNavFrame->addImage(SDL_Rect{ 0, 0, glyphSize, glyphSize },
4137 0xFFFFFFFF, "images/system/white.png", "left bumper img");
4138 leftBumperNavigationImg->disabled = true;
4139
4140 auto rightBumperNavigationTxt = uiNavFrame->addField("right bumper txt", 64);
4141 rightBumperNavigationTxt->setFont(navFont);
4142 rightBumperNavigationTxt->setVJustify(Field::justify_t::CENTER);
4143 rightBumperNavigationTxt->setHJustify(Field::justify_t::LEFT);
4144
4145 auto rightBumperNavigationImg = uiNavFrame->addImage(SDL_Rect{ 0, 0, glyphSize, glyphSize },
4146 0xFFFFFFFF, "images/system/white.png", "right bumper img");
4147 rightBumperNavigationImg->disabled = true;
4148
4149 auto leftTriggerNavigationTxt = uiNavFrame->addField("left trigger txt", 64);
4150 leftTriggerNavigationTxt->setFont(navFont);
4151 leftTriggerNavigationTxt->setHJustify(Field::justify_t::RIGHT);
4152 leftTriggerNavigationTxt->setVJustify(Field::justify_t::CENTER);
4153 leftTriggerNavigationTxt->setDisabled(true);
4154
4155 auto leftTriggerNavigationImg = uiNavFrame->addImage(SDL_Rect{ 0, 0, glyphSize, glyphSize },
4156 0xFFFFFFFF, "images/system/white.png", "left trigger img");
4157 leftTriggerNavigationImg->disabled = true;
4158
4159 auto rightTriggerNavigationTxt = uiNavFrame->addField("right trigger txt", 64);
4160 rightTriggerNavigationTxt->setFont(navFont);
4161 rightTriggerNavigationTxt->setVJustify(Field::justify_t::CENTER);
4162 rightTriggerNavigationTxt->setHJustify(Field::justify_t::LEFT);
4163
4164 auto rightTriggerNavigationImg = uiNavFrame->addImage(SDL_Rect{ 0, 0, glyphSize, glyphSize },
4165 0xFFFFFFFF, "images/system/white.png", "right trigger img");
4166 rightTriggerNavigationImg->disabled = true;
4167
4168 auto additionalNavigationTxt = uiNavFrame->addField("additional txt", 64);
4169 additionalNavigationTxt->setFont(navFont);
4170 additionalNavigationTxt->setVJustify(Field::justify_t::CENTER);
4171 additionalNavigationTxt->setHJustify(Field::justify_t::LEFT);
4172
4173 auto additionalNavigationImg = uiNavFrame->addImage(SDL_Rect{ 0, 0, glyphSize, glyphSize },
4174 0xFFFFFFFF, "images/system/white.png", "additional img");
4175 additionalNavigationImg->disabled = true;
4176 }
4177}
4178
4179void Player::HUD_t::updateUINavigation()
4180{
4181 if ( !hudFrame )
4182 {
4183 return;
4184 }
4185
4186 if ( !uiNavFrame )
4187 {
4188 createUINavigation(player.playernum);
4189 if ( !uiNavFrame )
4190 {
4191 return;
4192 }
4193 }
4194
4195 bool leftTriggerPressed = Input::inputs[player.playernum].consumeBinaryToggle("UINavLeftTrigger")
4196 && player.bControlEnabled && !gamePaused && !player.usingCommand();
4197 bool rightTriggerPressed = Input::inputs[player.playernum].consumeBinaryToggle("UINavRightTrigger")
4198 && player.bControlEnabled && !gamePaused && !player.usingCommand();
4199
4200 bShowUINavigation = false;
4201 if ( player.gui_mode != GUI_MODE_NONE
4202 && player.gui_mode != GUI_MODE_FOLLOWERMENU
4203 && player.gui_mode != GUI_MODE_CALLOUT
4204 && player.gui_mode != GUI_MODE_SIGN
4205 && player.isLocalPlayer() && !player.shootmode )
4206 {
4207 /*if ( player.bUseCompactGUIHeight() * Frame::virtualScreenX || (keystatus[SDLK_Y] && enableDebugKeys) )
4208 {
4209 bShowUINavigation = true;
4210 }*/
4211 bShowUINavigation = true;
4212 }
4213
4214 if ( !bShowUINavigation )
4215 {
4216 uiNavFrame->setDisabled(true);
4217 return;
4218 }
4219 uiNavFrame->setDisabled(false);
4220 uiNavFrame->setSize(SDL_Rect{ 0, 0, hudFrame->getSize().w, hudFrame->getSize().h });
4221
4222 if ( player.bUseCompactGUIWidth() &&
4223 (player.hud.levelupFrame && !player.hud.levelupFrame->isDisabled()) )
4224 {
4225 uiNavFrame->setInvisible(true);
4226 }
4227 else
4228 {
4229 uiNavFrame->setInvisible(false);
4230 }
4231
4232 auto leftBumperModule = player.GUI.handleModuleNavigation(true, true);
4233 auto leftBumperTxt = uiNavFrame->findField("left bumper txt");
4234 leftBumperTxt->setDisabled(true);
4235 auto leftBumperGlyph = uiNavFrame->findImage("left bumper img");
4236 leftBumperGlyph->disabled = true;
4237 auto rightBumperModule = player.GUI.handleModuleNavigation(true, false);
4238 auto rightBumperTxt = uiNavFrame->findField("right bumper txt");
4239 rightBumperTxt->setDisabled(true);
4240 auto rightBumperGlyph = uiNavFrame->findImage("right bumper img");
4241 rightBumperGlyph->disabled = true;
4242
4243 auto leftTriggerTxt = uiNavFrame->findField("left trigger txt");
4244 leftTriggerTxt->setDisabled(true);
4245 auto leftTriggerGlyph = uiNavFrame->findImage("left trigger img");
4246 leftTriggerGlyph->disabled = true;
4247 auto rightTriggerTxt = uiNavFrame->findField("right trigger txt");
4248 rightTriggerTxt->setDisabled(true);
4249 auto rightTriggerGlyph = uiNavFrame->findImage("right trigger img");
4250 rightTriggerGlyph->disabled = true;
4251
4252 auto additionalTxt = uiNavFrame->findField("additional txt");
4253 additionalTxt->setDisabled(true);
4254 auto additionalGlyph = uiNavFrame->findImage("additional img");
4255 additionalGlyph->disabled = true;
4256 if ( inputs.hasController(player.playernum) && !inputs.getVirtualMouse(player.playernum)->draw_cursor
4257 && (!player.bUseCompactGUIHeight() && !player.bUseCompactGUIWidth()) )
4258 {
4259 if ( leftBumperModule != Player::GUI_t::MODULE_NONE )
4260 {
4261 switch ( leftBumperModule )
4262 {
4263 case Player::GUI_t::MODULE_INVENTORY:
4264 case Player::GUI_t::MODULE_SPELLS:
4265 case Player::GUI_t::MODULE_HOTBAR:
4266 case Player::GUI_t::MODULE_CHARACTERSHEET:
4267 case Player::GUI_t::MODULE_CHEST:
4268 case Player::GUI_t::MODULE_SHOP:
4269 case Player::GUI_t::MODULE_ALCHEMY:
4270 leftBumperTxt->setDisabled(false);
4271 leftBumperTxt->setText("/");
4272 break;
4273 default:
4274 break;
4275 }
4276 }
4277 if ( rightBumperModule != Player::GUI_t::MODULE_NONE )
4278 {
4279 switch ( rightBumperModule )
4280 {
4281 case Player::GUI_t::MODULE_INVENTORY:
4282 case Player::GUI_t::MODULE_SPELLS:
4283 case Player::GUI_t::MODULE_HOTBAR:
4284 case Player::GUI_t::MODULE_CHARACTERSHEET:
4285 case Player::GUI_t::MODULE_CHEST:
4286 case Player::GUI_t::MODULE_SHOP:
4287 case Player::GUI_t::MODULE_ALCHEMY:
4288 rightBumperTxt->setDisabled(false);
4289 rightBumperTxt->setText(Language::get(4092));
4290 break;
4291 default:
4292 break;
4293 }
4294 }
4295 if ( player.GUI.activeModule == Player::GUI_t::MODULE_INVENTORY || player.GUI.activeModule == Player::GUI_t::MODULE_HOTBAR )
4296 {
4297
4298 }
4299 else if ( player.GUI.activeModule == Player::GUI_t::MODULE_CHARACTERSHEET )
4300 {
4301 auto selectedElement = player.characterSheet.selectedElement;
4302 if ( selectedElement >= Player::CharacterSheet_t::SHEET_STR
4303 && selectedElement <= Player::CharacterSheet_t::SHEET_WGT )
4304 {
4305 additionalTxt->setDisabled(false);
4306 additionalTxt->setText(Language::get(4111));
4307 }
4308 else if ( selectedElement == Player::CharacterSheet_t::SHEET_CHAR_RACE_SEX
4309 || selectedElement == Player::CharacterSheet_t::SHEET_CHAR_CLASS )
4310 {
4311 additionalTxt->setDisabled(false);
4312 additionalTxt->setText(Language::get(4111));
4313 }
4314 else if ( selectedElement == Player::CharacterSheet_t::SHEET_DUNGEON_FLOOR )
4315 {
4316 additionalTxt->setDisabled(false);
4317 additionalTxt->setText(Language::get(4112));
4318 }
4319 else if ( selectedElement == Player::CharacterSheet_t::SHEET_SKILL_LIST
4320 || selectedElement == Player::CharacterSheet_t::SHEET_OPEN_LOG
4321 || selectedElement == Player::CharacterSheet_t::SHEET_OPEN_MAP )
4322 {
4323 additionalTxt->setDisabled(false);
4324 // options to use specific text 'open log' etc
4325 //if ( selectedElement == Player::CharacterSheet_t::SHEET_SKILL_LIST )
4326 //{
4327 // additionalTxt->setText(Language::get(4095));
4328 //}
4329 //else if ( selectedElement == Player::CharacterSheet_t::SHEET_OPEN_LOG )
4330 //{
4331 // additionalTxt->setText(Language::get(4106));
4332 //}
4333 //else if ( selectedElement == Player::CharacterSheet_t::SHEET_OPEN_MAP )
4334 //{
4335 // additionalTxt->setText(Language::get(4105));
4336 //}
4337 additionalTxt->setText(Language::get(4107)); // activate
4338 }
4339 else if ( selectedElement == Player::CharacterSheet_t::SHEET_GOLD )
4340 {
4341 additionalTxt->setDisabled(false);
4342 if ( player.GUI.isDropdownActive() )
4343 {
4344 additionalTxt->setText(Language::get(4053));
4345 }
4346 else
4347 {
4348 additionalTxt->setText(Language::get(4108));
4349 }
4350 }
4351 else if ( selectedElement == Player::CharacterSheet_t::SHEET_TIMER )
4352 {
4353 additionalTxt->setDisabled(false);
4354 if ( player.characterSheet.showGameTimerAlways )
4355 {
4356 additionalTxt->setText(Language::get(4110));
4357 }
4358 else
4359 {
4360 additionalTxt->setText(Language::get(4109));
4361 }
4362 }
4363 if ( !additionalTxt->isDisabled() )
4364 {
4365 if ( selectedElement == Player::CharacterSheet_t::SHEET_GOLD
4366 && player.GUI.isDropdownActive() )
4367 {
4368 additionalGlyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuCancel");
4369 }
4370 else
4371 {
4372 additionalGlyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuConfirm");
4373 }
4374 }
4375 }
4376 }
4377
4378 int lowestLeftY = 8;
4379 int lowestRightY = 8;
4380
4381 int leftAnchorX = 0;
4382 int rightAnchorX = 0;
4383 PanelJustify_t justify = player.inventoryUI.inventoryPanelJustify;
4384 if ( player.inventoryUI.frame )
4385 {
4386 auto inventoryBgFrame = player.inventoryUI.frame->findFrame("inventory base");
4387
4388 Frame::image_t* invBaseImg = inventoryBgFrame->findImage("inventory base img");
4389
4390 if ( justify == PANEL_JUSTIFY_LEFT )
4391 {
4392 leftAnchorX = inventoryBgFrame->getSize().x + 8;
4393 leftAnchorX += invBaseImg->pos.w;
4394
4395 rightAnchorX = player.inventoryUI.frame->getSize().w - leftAnchorX;
4396 }
4397 else
4398 {
4399 rightAnchorX = inventoryBgFrame->getSize().x - 8;
4400 rightAnchorX += invBaseImg->pos.x;
4401
4402 leftAnchorX = player.inventoryUI.frame->getSize().w - rightAnchorX;
4403 }
4404 }
4405
4406 if ( inputs.hasController(player.playernum) && !inputs.getVirtualMouse(player.playernum)->draw_cursor
4407 && (!player.bUseCompactGUIHeight() && !player.bUseCompactGUIWidth()) )
4408 {
4409 if ( (player.GUI.activeModule == Player::GUI_t::MODULE_INVENTORY
4410 || player.GUI.activeModule == Player::GUI_t::MODULE_SPELLS
4411 || player.GUI.activeModule == Player::GUI_t::MODULE_HOTBAR
4412 || player.GUI.activeModule == Player::GUI_t::MODULE_CHARACTERSHEET
4413 || player.GUI.activeModule == Player::GUI_t::MODULE_SHOP
4414 || player.GUI.activeModule == Player::GUI_t::MODULE_CHEST) )
4415 {
4416 if ( !GenericGUI[player.playernum].tinkerGUI.bOpen && !GenericGUI[player.playernum].alchemyGUI.bOpen
4417 && !GenericGUI[player.playernum].featherGUI.bOpen
4418 && !GenericGUI[player.playernum].itemfxGUI.bOpen )
4419 {
4420 justify = PANEL_JUSTIFY_LEFT;
4421 leftTriggerGlyph->disabled = false;
4422 leftTriggerGlyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("UINavLeftTrigger");
4423 SDL_Rect textPos;
4424 textPos.x = (justify == PANEL_JUSTIFY_LEFT) ? leftAnchorX : rightAnchorX;
4425 textPos.y = 8;
4426 textPos.w = leftTriggerTxt->getTextObject()->getWidth();
4427 textPos.h = Font::get(leftTriggerTxt->getFont())->height() + 8;
4428 if ( justify == PANEL_JUSTIFY_LEFT )
4429 {
4430 leftTriggerTxt->setHJustify(Field::justify_t::LEFT);
4431 }
4432 else
4433 {
4434 leftTriggerTxt->setHJustify(Field::justify_t::RIGHT);
4435 }
4436
4437 SDL_Rect imgPos{0, 0, 0, 0};
4438 if ( auto imgGet = Image::get(leftTriggerGlyph->path.c_str()) )
4439 {
4440 imgPos.w = imgGet->getWidth();
4441 imgPos.h = imgGet->getHeight();
4442 }
4443 imgPos.x = (justify == PANEL_JUSTIFY_LEFT) ? leftAnchorX : rightAnchorX;
4444 imgPos.y = textPos.y - (imgPos.h - textPos.h) / 2;
4445
4446 if ( justify == PANEL_JUSTIFY_LEFT )
4447 {
4448 leftTriggerGlyph->pos = imgPos;
4449 textPos.x = leftTriggerGlyph->pos.x + leftTriggerGlyph->pos.w + 8;
4450 leftTriggerTxt->setSize(textPos);
4451 }
4452 else
4453 {
4454 imgPos.x -= imgPos.w;
4455 leftTriggerGlyph->pos = imgPos;
4456 textPos.x = leftTriggerGlyph->pos.x - 8 - textPos.w;
4457 leftTriggerTxt->setSize(textPos);
4458 }
4459 leftTriggerTxt->setDisabled(false);
4460 if ( player.inventory_mode == INVENTORY_MODE_ITEM )
4461 {
4462 leftTriggerTxt->setText(Language::get(4093));
4463 }
4464 else if ( player.inventory_mode == INVENTORY_MODE_SPELL )
4465 {
4466 leftTriggerTxt->setText(Language::get(4094));
4467 }
4468 }
4469
4470 if ( !player.inventoryUI.chestGUI.bOpen && !player.shopGUI.bOpen
4471 && !GenericGUI[player.playernum].tinkerGUI.bOpen && !GenericGUI[player.playernum].alchemyGUI.bOpen
4472 && !GenericGUI[player.playernum].featherGUI.bOpen
4473 && !GenericGUI[player.playernum].itemfxGUI.bOpen )
4474 {
4475 justify = PANEL_JUSTIFY_RIGHT;
4476 rightTriggerGlyph->disabled = false;
4477 rightTriggerGlyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("UINavRightTrigger");
4478 SDL_Rect textPos;
4479 textPos.x = (justify == PANEL_JUSTIFY_LEFT) ? leftAnchorX : rightAnchorX;
4480 textPos.y = 8;
4481 textPos.w = rightTriggerTxt->getTextObject()->getWidth();
4482 textPos.h = Font::get(rightTriggerTxt->getFont())->height() + 8;
4483 if ( justify == PANEL_JUSTIFY_LEFT )
4484 {
4485 rightTriggerTxt->setHJustify(Field::justify_t::LEFT);
4486 }
4487 else
4488 {
4489 rightTriggerTxt->setHJustify(Field::justify_t::RIGHT);
4490 }
4491
4492 SDL_Rect imgPos{ 0, 0, 0, 0 };
4493 if ( auto imgGet = Image::get(rightTriggerGlyph->path.c_str()) )
4494 {
4495 imgPos.w = imgGet->getWidth();
4496 imgPos.h = imgGet->getHeight();
4497 }
4498 imgPos.x = (justify == PANEL_JUSTIFY_LEFT) ? leftAnchorX : rightAnchorX;
4499 imgPos.y = textPos.y - (imgPos.h - textPos.h) / 2;
4500
4501 if ( justify == PANEL_JUSTIFY_LEFT )
4502 {
4503 rightTriggerGlyph->pos = imgPos;
4504 textPos.x = rightTriggerGlyph->pos.x + rightTriggerGlyph->pos.w + 8;
4505 rightTriggerTxt->setSize(textPos);
4506 }
4507 else
4508 {
4509 imgPos.x -= imgPos.w;
4510 rightTriggerGlyph->pos = imgPos;
4511 textPos.x = rightTriggerGlyph->pos.x - 8 - textPos.w;
4512 rightTriggerTxt->setSize(textPos);
4513 }
4514 rightTriggerTxt->setDisabled(false);
4515 rightTriggerTxt->setText(Language::get(4095));
4516
4517 if ( !additionalTxt->isDisabled() )
4518 {
4519 if ( justify == PANEL_JUSTIFY_LEFT )
4520 {
4521 additionalTxt->setHJustify(Field::justify_t::LEFT);
4522 }
4523 else
4524 {
4525 additionalTxt->setHJustify(Field::justify_t::RIGHT);
4526 }
4527 additionalGlyph->disabled = false;
4528 SDL_Rect textPos;
4529 textPos.w = additionalTxt->getTextObject()->getWidth();
4530 textPos.h = Font::get(additionalTxt->getFont())->height() + 8;
4531 if ( justify == PANEL_JUSTIFY_LEFT )
4532 {
4533 textPos.x = rightTriggerTxt->getSize().x;
4534 }
4535 else
4536 {
4537 textPos.x = rightTriggerTxt->getSize().x + rightTriggerTxt->getSize().w - textPos.w;
4538 }
4539 textPos.y = std::max(lowestRightY, rightTriggerTxt->getSize().y + rightTriggerTxt->getSize().h);
4540
4541 SDL_Rect imgPos{ 0, 0, 0, 0 };
4542 if ( auto imgGet = Image::get(additionalGlyph->path.c_str()) )
4543 {
4544 imgPos.w = imgGet->getWidth();
4545 imgPos.h = imgGet->getHeight();
4546 }
4547 imgPos.x = rightTriggerGlyph->pos.x + rightTriggerGlyph->pos.w / 2 - imgPos.w / 2;
4548 imgPos.y = textPos.y - (imgPos.h - textPos.h) / 2;
4549 additionalGlyph->pos = imgPos;
4550
4551 additionalTxt->setSize(textPos);
4552 }
4553 }
4554 }
4555
4556 if ( leftTriggerPressed && !leftTriggerTxt->isDisabled() )
4557 {
4558 leftTriggerPressed = false;
4559 if ( !inputs.getUIInteraction(player.playernum)->selectedItem && !player.GUI.isDropdownActive()
4560 && (inputs.hasController(player.playernum) && !player.shootmode
4561 && (player.GUI.activeModule == Player::GUI_t::MODULE_INVENTORY
4562 || player.GUI.activeModule == Player::GUI_t::MODULE_SPELLS
4563 || player.GUI.activeModule == Player::GUI_t::MODULE_HOTBAR
4564 || player.GUI.activeModule == Player::GUI_t::MODULE_CHARACTERSHEET
4565 || player.GUI.activeModule == Player::GUI_t::MODULE_CHEST
4566 || player.GUI.activeModule == Player::GUI_t::MODULE_SHOP)) )
4567 {
4568 player.gui_mode = GUI_MODE_INVENTORY;
4569 if ( player.shootmode )
4570 {
4571 player.openStatusScreen(GUI_MODE_INVENTORY, INVENTORY_MODE_ITEM);
4572 }
4573 player.inventoryUI.cycleInventoryTab();
4574 }
4575 }
4576
4577 if ( rightTriggerPressed && !rightTriggerTxt->isDisabled() )
4578 {
4579 rightTriggerPressed = false;
4580 if ( !inputs.getUIInteraction(player.playernum)->selectedItem && !player.GUI.isDropdownActive()
4581 && (inputs.hasController(player.playernum) && !player.shootmode
4582 && (player.GUI.activeModule == Player::GUI_t::MODULE_INVENTORY
4583 || player.GUI.activeModule == Player::GUI_t::MODULE_SPELLS
4584 || player.GUI.activeModule == Player::GUI_t::MODULE_HOTBAR
4585 || player.GUI.activeModule == Player::GUI_t::MODULE_CHARACTERSHEET)) )
4586 {
4587 if ( !player.skillSheet.bSkillSheetOpen )
4588 {
4589 player.skillSheet.openSkillSheet();
4590 }
4591 }
4592 }
4593
4594 if ( !leftTriggerTxt->isDisabled() )
4595 {
4596 lowestLeftY = std::max(lowestLeftY, leftTriggerTxt->getSize().y + leftTriggerTxt->getSize().h);
4597 lowestLeftY = std::max(lowestLeftY, leftTriggerGlyph->pos.y + leftTriggerGlyph->pos.h);
4598 }
4599 if ( !rightTriggerTxt->isDisabled() )
4600 {
4601 lowestRightY = std::max(lowestRightY, rightTriggerTxt->getSize().y + rightTriggerTxt->getSize().h);
4602 lowestRightY = std::max(lowestRightY, rightTriggerGlyph->pos.y + rightTriggerGlyph->pos.h);
4603 }
4604
4605 if ( !leftBumperTxt->isDisabled() )
4606 {
4607 leftBumperGlyph->disabled = false;
4608 leftBumperGlyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("UINavLeftBumper");
4609 SDL_Rect textPos;
4610 textPos.x = leftTriggerGlyph->pos.x;
4611 textPos.y = lowestLeftY;
4612 textPos.w = leftBumperTxt->getTextObject()->getWidth();
4613 textPos.h = Font::get(leftBumperTxt->getFont())->height() + 8;
4614
4615 SDL_Rect imgPos{ 0, 0, 0, 0 };
4616 if ( auto imgGet = Image::get(leftBumperGlyph->path.c_str()) )
4617 {
4618 imgPos.w = imgGet->getWidth();
4619 imgPos.h = imgGet->getHeight();
4620 }
4621 imgPos.x = textPos.x;
4622 imgPos.y = textPos.y - (imgPos.h - textPos.h) / 2;
4623 leftBumperGlyph->pos = imgPos;
4624
4625 textPos.x = imgPos.x + 4 + imgPos.w;
4626 leftBumperTxt->setSize(textPos);
4627 }
4628 if ( !rightBumperTxt->isDisabled() )
4629 {
4630 rightBumperGlyph->disabled = false;
4631 rightBumperGlyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("UINavRightBumper");
4632 SDL_Rect textPos;
4633 textPos.x = leftBumperTxt->getSize().x + leftBumperTxt->getSize().w + 8;
4634 textPos.y = leftBumperTxt->getSize().y;
4635 textPos.w = rightBumperTxt->getTextObject()->getWidth();
4636 textPos.h = Font::get(rightBumperTxt->getFont())->height() + 8;
4637
4638 SDL_Rect imgPos{ 0, 0, 0, 0 };
4639 if ( auto imgGet = Image::get(rightBumperGlyph->path.c_str()) )
4640 {
4641 imgPos.w = imgGet->getWidth();
4642 imgPos.h = imgGet->getHeight();
4643 }
4644 imgPos.x = textPos.x;
4645 imgPos.y = textPos.y - (imgPos.h - textPos.h) / 2;
4646
4647 textPos.x += imgPos.w + 4;
4648 rightBumperTxt->setSize(textPos);
4649 rightBumperGlyph->pos = imgPos;
4650 }
4651 }
4652
4653 auto magicButton = uiNavFrame->findButton("magic button");
4654 auto magicButtonGlyph = uiNavFrame->findImage("magic button glyph");
4655 auto itemsButton = uiNavFrame->findButton("items button");
4656 auto itemsButtonGlyph = uiNavFrame->findImage("items button glyph");
4657 auto statusButton = uiNavFrame->findButton("status button");
4658 auto statusButtonGlyph = uiNavFrame->findImage("status button glyph");
4659 auto skillsButton = uiNavFrame->findButton("skills button");
4660 auto skillsButtonGlyph = uiNavFrame->findImage("skills button glyph");
4661
4662 static ConsoleVariable<bool> cvar_spell_unread_blink("/spell_unread_blink", true);
4663 if ( *cvar_spell_unread_blink
4664 && player.magic.bHasUnreadNewSpell && ticks % TICKS_PER_SECOND50 >= TICKS_PER_SECOND50 / 2 )
4665 {
4666 magicButton->setTextColor(hudColors.characterSheetGreen);
4667 magicButton->setTextHighlightColor(hudColors.characterSheetGreen);
4668 }
4669 else
4670 {
4671 magicButton->setTextColor(makeColor(255, 255, 255, 255));
4672 magicButton->setTextHighlightColor(makeColor(255, 255, 255, 255));
4673 }
4674
4675 struct ButtonsAndGlyphs {
4676 std::string name;
4677 Button* button = nullptr;
4678 Frame::image_t* glyph = nullptr;
4679 std::string inputName;
4680 CompactLayoutModes layoutMode;
4681 ButtonsAndGlyphs(std::string _name,
4682 Button* _button,
4683 Frame::image_t* _glyph,
4684 std::string _inputName,
4685 CompactLayoutModes _layoutMode) :
4686 name(_name),
4687 button(_button),
4688 glyph(_glyph),
4689 inputName(_inputName),
4690 layoutMode(_layoutMode)
4691 {
4692 }
4693 };
4694 std::vector<ButtonsAndGlyphs> allButtonsAndGlyphs;
4695 allButtonsAndGlyphs.emplace_back(
4696 ButtonsAndGlyphs{ "magic button", magicButton, magicButtonGlyph,
4697 "UINavLeftTrigger", COMPACT_LAYOUT_INVENTORY });
4698 allButtonsAndGlyphs.emplace_back(
4699 ButtonsAndGlyphs{ "status button", statusButton, statusButtonGlyph,
4700 "UINavRightTrigger", COMPACT_LAYOUT_INVENTORY });
4701 allButtonsAndGlyphs.emplace_back(
4702 ButtonsAndGlyphs{ "items button", itemsButton, itemsButtonGlyph,
4703 "UINavLeftTrigger", COMPACT_LAYOUT_CHARSHEET });
4704 allButtonsAndGlyphs.emplace_back(
4705 ButtonsAndGlyphs{ "skills button", skillsButton, skillsButtonGlyph,
4706 "UINavRightTrigger", COMPACT_LAYOUT_CHARSHEET });
4707
4708 int buttonWidth = 98;
4709 int buttonHeight = 38;
4710 int alignPaddingX = 2;
4711 int leftAlignX = uiNavFrame->getSize().w / 2 - buttonWidth - alignPaddingX;
4712 int rightAlignX = uiNavFrame->getSize().w / 2 + alignPaddingX;
4713 int topAlignY = 34;
4714 int bottomAlignY = topAlignY + 52;
4715
4716 int numButtonsToShow = 2;
4717
4718 for ( auto& buttonAndGlyph : allButtonsAndGlyphs )
4719 {
4720 auto& button = buttonAndGlyph.button;
4721 button->setDisabled(true);
4722 SDL_Rect buttonPos = button->getSize();
4723 auto& glyph = buttonAndGlyph.glyph;
4724 glyph->disabled = true;
4725
4726 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor && inputs.hasController(player.playernum) )
4727 {
4728 button->setColor(makeColor(255, 255, 255, 255));
4729 }
4730 else
4731 {
4732 button->setColor(makeColor(255, 255, 255, 255));
4733 }
4734
4735 if ( player.bUseCompactGUIHeight() || player.bUseCompactGUIWidth() )
4736 {
4737 if ( player.inventoryUI.chestGUI.bOpen )
4738 {
4739 if ( buttonAndGlyph.inputName == "UINavRightTrigger" )
4740 {
4741 button->setInvisible(true);
4742 continue;
4743 }
4744 }
4745 if ( player.shopGUI.bOpen )
4746 {
4747 if ( buttonAndGlyph.inputName == "UINavRightTrigger"
4748 || (inputs.getVirtualMouse(player.playernum)->draw_cursor && player.bUseCompactGUIWidth()) )
4749 {
4750 button->setInvisible(true);
4751 continue;
4752 }
4753 }
4754 if ( GenericGUI[player.playernum].isGUIOpen() || player.GUI.isDropdownActive()
4755 || player.minimap.mapWindow || player.messageZone.logWindow || player.hud.statusFxFocusedWindowActive )
4756 {
4757 button->setDisabled(true);
4758 continue;
4759 }
4760 }
4761
4762 if ( buttonAndGlyph.name == "magic button" || buttonAndGlyph.name == "items button" )
4763 {
4764 if ( inputs.bPlayerUsingKeyboardControl(player.playernum)
4765 && inputs.getVirtualMouse(player.playernum)->draw_cursor
4766 && (!player.bUseCompactGUIHeight() && !player.bUseCompactGUIWidth())
4767 && !GenericGUI[player.playernum].isGUIOpen()
4768 && !player.minimap.mapWindow
4769 && !player.messageZone.logWindow
4770 && !player.hud.statusFxFocusedWindowActive )
4771 {
4772 if ( player.inventory_mode == INVENTORY_MODE_ITEM )
4773 {
4774 if ( buttonAndGlyph.name == "magic button" )
4775 {
4776 button->setDisabled(false);
4777 glyph->disabled = false;
4778
4779 buttonPos.x = leftAnchorX;
4780 buttonPos.y = 8;
4781 buttonAndGlyph.inputName = "Spell List";
4782 }
4783 }
4784 else if ( player.inventory_mode == INVENTORY_MODE_SPELL )
4785 {
4786 if ( buttonAndGlyph.name == "items button" )
4787 {
4788 button->setDisabled(false);
4789 glyph->disabled = false;
4790
4791 buttonPos.x = leftAnchorX;
4792 buttonPos.y = 8;
4793 buttonAndGlyph.inputName = "Spell List";
4794 }
4795 }
4796 }
4797 else if ( player.bUseCompactGUIHeight() || player.bUseCompactGUIWidth() )
4798 {
4799 if ( !player.bUseCompactGUIWidth() )
4800 {
4801 // align next to inventory
4802 buttonPos.x = leftAnchorX;
4803 }
4804 else
4805 {
4806 buttonPos.x = leftAlignX;
4807 }
4808 if ( numButtonsToShow < 4 )
4809 {
4810 buttonPos.y = topAlignY;
4811 if ( !player.bUseCompactGUIWidth() )
4812 {
4813 if ( players[player.playernum]->hud.xpFrame )
4814 {
4815 SDL_Rect xpFramePos = players[player.playernum]->hud.xpFrame->getSize();
4816 buttonPos.y = xpFramePos.y + 8;
4817 }
4818 }
4819
4820 if ( compactLayoutMode == COMPACT_LAYOUT_INVENTORY )
4821 {
4822 if ( buttonAndGlyph.name == "items button" && player.inventory_mode == INVENTORY_MODE_ITEM )
4823 {
4824 // leave disabled
4825 button->setDisabled(true);
4826 glyph->disabled = true;
4827 }
4828 else if ( buttonAndGlyph.name == "magic button" && player.inventory_mode == INVENTORY_MODE_SPELL )
4829 {
4830 // leave disabled
4831 button->setDisabled(true);
4832 glyph->disabled = true;
4833 }
4834 else
4835 {
4836 button->setDisabled(false);
4837 glyph->disabled = false;
4838 }
4839 }
4840 else if ( buttonAndGlyph.layoutMode == compactLayoutMode )
4841 {
4842 button->setDisabled(false);
4843 glyph->disabled = false;
4844 }
4845 }
4846 else
4847 {
4848 button->setDisabled(false);
4849 glyph->disabled = false;
4850 if ( buttonAndGlyph.name == "magic button" )
4851 {
4852 buttonPos.y = topAlignY;
4853 }
4854 else if ( buttonAndGlyph.name == "items button" )
4855 {
4856 buttonPos.y = bottomAlignY;
4857 }
4858 }
4859 }
4860 buttonPos.w = buttonWidth;
4861 buttonPos.h = buttonHeight;
4862 button->setSize(buttonPos);
4863 }
4864 if ( buttonAndGlyph.name == "status button" || buttonAndGlyph.name == "skills button" )
4865 {
4866 if ( inputs.bPlayerUsingKeyboardControl(player.playernum)
4867 && inputs.getVirtualMouse(player.playernum)->draw_cursor
4868 && (!player.bUseCompactGUIHeight() && !player.bUseCompactGUIWidth()) )
4869 {
4870 // leave disabled
4871 }
4872 else if ( player.bUseCompactGUIHeight() || player.bUseCompactGUIWidth() )
4873 {
4874 if ( !player.bUseCompactGUIWidth() )
4875 {
4876 // align next to right panel
4877 buttonPos.x = rightAnchorX - buttonPos.w;
4878 }
4879 else
4880 {
4881 buttonPos.x = rightAlignX;
4882 }
4883 if ( numButtonsToShow < 4 )
4884 {
4885 buttonPos.y = topAlignY;
4886 if ( !player.bUseCompactGUIWidth() )
4887 {
4888 if ( players[player.playernum]->hud.xpFrame )
4889 {
4890 SDL_Rect xpFramePos = players[player.playernum]->hud.xpFrame->getSize();
4891 buttonPos.y = xpFramePos.y + 8;
4892 }
4893 }
4894 if ( buttonAndGlyph.layoutMode == compactLayoutMode )
4895 {
4896 button->setDisabled(false);
4897 glyph->disabled = false;
4898 }
4899 }
4900 else
4901 {
4902 button->setDisabled(false);
4903 glyph->disabled = false;
4904 if ( buttonAndGlyph.name == "status button" )
4905 {
4906 buttonPos.y = topAlignY;
4907 }
4908 else if ( buttonAndGlyph.name == "skills button" )
4909 {
4910 buttonPos.y = bottomAlignY;
4911 }
4912 }
4913 }
4914 buttonPos.w = buttonWidth;
4915 buttonPos.h = buttonHeight;
4916 button->setSize(buttonPos);
4917 }
4918
4919 if ( !button->isDisabled() )
4920 {
4921 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding(buttonAndGlyph.inputName.c_str());
4922 glyph->ontop = true;
4923 if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
4924 {
4925 glyph->disabled = false;
4926 }
4927 else
4928 {
4929 glyph->disabled = false;
4930 }
4931 if ( auto imgGet = Image::get(glyph->path.c_str()) )
4932 {
4933 glyph->pos.w = (int)imgGet->getWidth();
4934 glyph->pos.h = (int)imgGet->getHeight();
4935 }
4936
4937 glyph->pos.x = button->getSize().x + button->getSize().w / 2 - glyph->pos.w / 2; // center the x for the glyph
4938 const int glyphToImgPadY = 8;
4939 glyph->pos.y = button->getSize().y + button->getSize().h - glyphToImgPadY; // just below the button with some padding
4940 }
4941 button->setInvisible(button->isDisabled());
4942 }
4943
4944 for ( auto& buttonAndGlyph : allButtonsAndGlyphs )
4945 {
4946 auto& button = buttonAndGlyph.button;
4947 if ( !button->isDisabled() && !button->isInvisible() )
4948 {
4949 if ( player.GUI.bModuleAccessibleWithMouse(Player::GUI_t::MODULE_INVENTORY)
4950 && inputs.getVirtualMouse(player.playernum)->draw_cursor
4951 && button->isHighlighted() )
4952 {
4953 player.GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_INVENTORY);
4954 SDL_Rect pos = button->getAbsoluteSize();
4955 // make sure to adjust absolute size to camera viewport
4956 pos.x -= player.camera_virtualx1();
4957 pos.y -= player.camera_virtualy1();
4958 player.hud.setCursorDisabled(false);
4959 player.hud.updateCursorAnimation(pos.x - 1, pos.y - 1, pos.w, pos.h, inputs.getVirtualMouse(player.playernum)->draw_cursor);
4960 }
4961
4962 if ( buttonAndGlyph.inputName == "UINavLeftTrigger" && leftTriggerPressed
4963 && player.GUI.bModuleAccessibleWithMouse(Player::GUI_t::MODULE_INVENTORY) )
4964 {
4965 leftTriggerPressed = false;
4966 button->activate();
4967 }
4968 else if ( buttonAndGlyph.inputName == "UINavRightTrigger" && rightTriggerPressed
4969 && player.GUI.bModuleAccessibleWithMouse(Player::GUI_t::MODULE_INVENTORY) )
4970 {
4971 rightTriggerPressed = false;
4972 button->activate();
4973 }
4974 }
4975 }
4976}
4977
4978bool StatusEffectQueue_t::insertEffect(int effectID, int spellID)
4979{
4980 if ( spellID >= 0 && spellID < NUM_SPELLS )
4981 {
4982 effectID = spellID + kSpellEffectOffset;
4983 if ( StatusEffectDefinitions_t::sustainedSpellDefinitionExists(effectID) )
4984 {
4985 if ( StatusEffectDefinitions_t::getSustainedSpell(effectID).neverDisplay )
4986 {
4987 return false;
4988 }
4989 }
4990 }
4991 if ( (effectID >= 0 && effectID < NUMEFFECTS)
4992 || effectID == kEffectBread
4993 || effectID == kEffectBloodHunger
4994 || effectID == kEffectAutomatonHunger
4995 || effectID >= kSpellEffectOffset )
4996 {
4997 if ( StatusEffectDefinitions_t::effectDefinitionExists(effectID) )
4998 {
4999 if ( StatusEffectDefinitions_t::getEffect(effectID).neverDisplay )
5000 {
5001 return false;
5002 }
5003 }
5004 for ( auto& q : effectQueue )
5005 {
5006 if ( effectID == q.effect )
5007 {
5008 return false;
5009 }
5010 }
5011 }
5012
5013 effectQueue.push_back(StatusEffectQueueEntry_t(effectID));
5014 effectQueue.back().pos.x = getBaseEffectPosX();
5015 effectQueue.back().pos.y = getBaseEffectPosY();
5016 notificationQueue.push_back(StatusEffectQueueEntry_t(effectID));
5017 notificationQueue.back().pos.x = getBaseEffectPosX();
5018 notificationQueue.back().pos.y = getBaseEffectPosY();
5019
5020 // fall back if notificationTargetPosition doesn't have an effect to go to.
5021 notificationQueue.back().notificationTargetPosition.x = 0;
5022 notificationQueue.back().notificationTargetPosition.y = statusEffectFrame->getSize().h - notificationQueue.back().notificationTargetPosition.h;
5023 requiresAnimUpdate = true;
5024 return true;
5025}
5026
5027std::string StatusEffectQueue_t::StatusEffectDefinitions_t::getEffectImgPath(StatusEffectQueue_t::EffectDefinitionEntry_t& entry, int variation)
5028{
5029 if ( entry.imgPath == "" )
5030 {
5031 if ( entry.imgPathVariations.size() > 0 && variation >= 0 )
5032 {
5033 return entry.imgPathVariations[std::min(variation, (int)entry.imgPathVariations.size() - 1)];
5034 }
5035 node_t* spellImageNode = nullptr;
5036 int spellID = entry.useSpellIDForImg;
5037 if ( variation >= 0 )
5038 {
5039 spellID = entry.useSpellIDForImgVariations[std::min(variation, (int)entry.useSpellIDForImgVariations.size() - 1)];
5040 }
5041 if ( spellID >= 0 && spellID < NUM_SPELLS )
5042 {
5043 spellImageNode = ItemTooltips.getSpellNodeFromSpellID(spellID);
5044 }
5045 if ( spellImageNode )
5046 {
5047 string_t* string = (string_t*)spellImageNode->element;
5048 if ( string )
5049 {
5050 return string->data;
5051 }
5052 }
5053 return "images/sprites/null.png";
5054 }
5055 if ( entry.imgPath == "" )
5056 {
5057 return "images/sprites/null.png";
5058 }
5059 return entry.imgPath;
5060}
5061
5062void StatusEffectQueueEntry_t::setAnimatePosition(int destx, int desty, int destw, int desth)
5063{
5064 animateStartX = pos.x;
5065 animateStartY = pos.y;
5066 animateStartW = pos.w;
5067 animateStartH = pos.h;
5068 animateSetpointX = destx;
5069 animateSetpointY = desty;
5070 animateSetpointW = destw;
5071 animateSetpointH = desth;
5072 animateX = 0.0;
5073 animateY = 0.0;
5074 animateW = 0.0;
5075 animateH = 0.0;
5076}
5077
5078void StatusEffectQueueEntry_t::setAnimatePosition(int destx, int desty)
5079{
5080 animateStartX = pos.x;
5081 animateStartY = pos.y;
5082 animateStartW = pos.w;
5083 animateStartH = pos.h;
5084 animateSetpointX = destx;
5085 animateSetpointY = desty;
5086 animateSetpointW = 0;
5087 animateSetpointH = 0;
5088 animateX = 0.0;
5089 animateY = 0.0;
5090 animateW = 0.0;
5091 animateH = 0.0;
5092}
5093
5094const real_t kStatusEffectQueueAnimSpeedMult = 4.0;
5095
5096void StatusEffectQueueEntry_t::animate()
5097{
5098 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
5099 real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - animateX)) / (5.0 / kStatusEffectQueueAnimSpeedMult);
5100 real_t setpointDiffY = fpsScale * std::max(.1, (1.0 - animateY)) / (5.0 / kStatusEffectQueueAnimSpeedMult);
5101 animateX += setpointDiffX;
5102 animateY += setpointDiffY;
5103 animateX = std::min(1.0, animateX);
5104 animateY = std::min(1.0, animateY);
5105
5106 int destX = animateSetpointX - animateStartX;
5107 int destY = animateSetpointY - animateStartY;
5108
5109 pos.x = animateStartX + destX * animateX;
5110 pos.y = animateStartY + destY * animateY;
5111}
5112
5113void StatusEffectQueue_t::loadStatusEffectsJSON()
5114{
5115 if ( !PHYSFS_getRealDir("/data/status_effects.json") )
5116 {
5117 printlog("[JSON]: Error: Could not find file: data/status_effects.json");
5118 }
5119 else
5120 {
5121 std::string inputPath = PHYSFS_getRealDir("/data/status_effects.json");
5122 inputPath.append("/data/status_effects.json");
5123
5124 File* fp = FileIO::open(inputPath.c_str(), "rb");
5125 if ( !fp )
5126 {
5127 printlog("[JSON]: Error: Could not open json file %s", inputPath.c_str());
5128 }
5129 else
5130 {
5131 char buf[65536];
5132 int count = fp->read(buf, sizeof(buf[0]), sizeof(buf));
5133 buf[count] = '\0';
5134 rapidjson::StringStream is(buf);
5135 FileIO::close(fp);
5136
5137 rapidjson::Document d;
5138 d.ParseStream(is);
5139 if ( !d.HasMember("version") )
5140 {
5141 printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str());
5142 }
5143 else
5144 {
5145 StatusEffectDefinitions_t::reset();
5146 int defaultTooltipWidth = 200;
5147 if ( d.HasMember("default_tooltip_width") )
5148 {
5149 defaultTooltipWidth = d["default_tooltip_width"].GetInt();
5150 }
5151 if ( d.HasMember("colors") )
5152 {
5153 if ( d["colors"].HasMember("notification_text") )
5154 {
5155 StatusEffectDefinitions_t::notificationTextColor = makeColor(
5156 d["colors"]["notification_text"]["r"].GetInt(),
5157 d["colors"]["notification_text"]["g"].GetInt(),
5158 d["colors"]["notification_text"]["b"].GetInt(),
5159 d["colors"]["notification_text"]["a"].GetInt());
5160 }
5161 if ( d["colors"].HasMember("tooltip_desc_text") )
5162 {
5163 StatusEffectDefinitions_t::tooltipDescColor = makeColor(
5164 d["colors"]["tooltip_desc_text"]["r"].GetInt(),
5165 d["colors"]["tooltip_desc_text"]["g"].GetInt(),
5166 d["colors"]["tooltip_desc_text"]["b"].GetInt(),
5167 d["colors"]["tooltip_desc_text"]["a"].GetInt());
5168 }
5169 if ( d["colors"].HasMember("tooltip_heading_text") )
5170 {
5171 StatusEffectDefinitions_t::tooltipHeadingColor = makeColor(
5172 d["colors"]["tooltip_heading_text"]["r"].GetInt(),
5173 d["colors"]["tooltip_heading_text"]["g"].GetInt(),
5174 d["colors"]["tooltip_heading_text"]["b"].GetInt(),
5175 d["colors"]["tooltip_heading_text"]["a"].GetInt());
5176 }
5177 }
5178 if ( d.HasMember("notification_font") )
5179 {
5180 StatusEffectDefinitions_t::notificationFont = d["notification_font"].GetString();
5181 }
5182 if ( d.HasMember("sustained_effects") )
5183 {
5184 for ( rapidjson::Value::ConstMemberIterator itr = d["sustained_effects"].MemberBegin();
5185 itr != d["sustained_effects"].MemberEnd(); ++itr )
5186 {
5187 int id = -1;
5188 if ( itr->value.HasMember("id") )
5189 {
5190 id = itr->value["id"].GetInt();
5191 }
5192 int spellID = -1;
5193 if ( itr->value.HasMember("spell_id") )
5194 {
5195 spellID = itr->value["spell_id"].GetInt();
5196 }
5197 StatusEffectDefinitions_t::allSustainedSpells.insert(
5198 std::make_pair(spellID, EffectDefinitionEntry_t()));
5199 auto& entry = StatusEffectDefinitions_t::allSustainedSpells[spellID];
5200 entry.effect_id = id;
5201 entry.spell_id = spellID;
5202 entry.internal_name = itr->name.GetString();
5203 if ( itr->value["name"].IsArray() )
5204 {
5205 for ( auto arr = itr->value["name"].Begin();
5206 arr != itr->value["name"].End(); ++arr )
5207 {
5208 entry.nameVariations.push_back(arr->GetString());
5209 }
5210 }
5211 else
5212 {
5213 entry.name = itr->value["name"].GetString();
5214 }
5215 std::string buf = itr->value["desc"].GetString();
5216 entry.desc = "\x1E ";
5217 int index = 0;
5218 for ( auto s : buf )
5219 {
5220 if ( index == 0 && (buf[0] == '+' || buf[0] == '-') )
5221 {
5222 entry.desc = "";
5223 }
5224 entry.desc += s;
5225 if ( s == '\n' )
5226 {
5227 if ( index + 1 < buf.size() )
5228 {
5229 if ( buf[index + 1] == '+' || buf[index + 1] == '-' )
5230 {
5231 // skip adding dot
5232 ++index;
5233 continue;
5234 }
5235 }
5236 entry.desc += "\x1E ";
5237 }
5238 ++index;
5239 }
5240 entry.imgPath = itr->value["img_path"].GetString();
5241 entry.useSpellIDForImg = itr->value["img_from_spell_id"].GetInt();
5242 entry.neverDisplay = false;
5243 if ( itr->value.HasMember("never_display") )
5244 {
5245 entry.neverDisplay = itr->value["never_display"].GetBool();
5246 }
5247 entry.tooltipWidth = defaultTooltipWidth;
5248 if ( itr->value.HasMember("tooltip_width") )
5249 {
5250 entry.tooltipWidth = itr->value["tooltip_width"].GetInt();
5251 }
5252 }
5253 }
5254 if ( d.HasMember("effects") )
5255 {
5256 for ( rapidjson::Value::ConstMemberIterator itr = d["effects"].MemberBegin();
5257 itr != d["effects"].MemberEnd(); ++itr )
5258 {
5259 int id = -1;
5260 if ( itr->value.HasMember("id") )
5261 {
5262 id = itr->value["id"].GetInt();
5263 }
5264 StatusEffectDefinitions_t::allEffects.insert(
5265 std::make_pair(id, EffectDefinitionEntry_t()));
5266 auto& entry = StatusEffectDefinitions_t::allEffects[id];
5267 entry.effect_id = id;
5268 if ( itr->value["name"].IsArray() )
5269 {
5270 for ( auto arr = itr->value["name"].Begin();
5271 arr != itr->value["name"].End(); ++arr )
5272 {
5273 entry.nameVariations.push_back(arr->GetString());
5274 }
5275 }
5276 else
5277 {
5278 entry.name = itr->value["name"].GetString();
5279 }
5280 if ( itr->value["desc"].IsArray() )
5281 {
5282 for ( auto arr = itr->value["desc"].Begin();
5283 arr != itr->value["desc"].End(); ++arr )
5284 {
5285 std::string buf = arr->GetString();
5286 int index = 0;
5287 std::string formattedStr = "\x1E ";
5288 for ( auto s : buf )
5289 {
5290 if ( index == 0 && (buf[0] == '+' || buf[0] == '-') )
5291 {
5292 formattedStr = "";
5293 }
5294 formattedStr += s;
5295 if ( s == '\n' )
5296 {
5297 if ( index + 1 < buf.size() )
5298 {
5299 if ( buf[index + 1] == '+' || buf[index + 1] == '-' )
5300 {
5301 // skip adding dot
5302 ++index;
5303 continue;
5304 }
5305 }
5306 formattedStr += "\x1E ";
5307 }
5308 ++index;
5309 }
5310 entry.descVariations.push_back(formattedStr);
5311 }
5312 }
5313 else
5314 {
5315 std::string buf = itr->value["desc"].GetString();
5316 entry.desc = "\x1E ";
5317 int index = 0;
5318 for ( auto s : buf )
5319 {
5320 if ( index == 0 && (buf[0] == '+' || buf[0] == '-') )
5321 {
5322 entry.desc = "";
5323 }
5324 entry.desc += s;
5325 if ( s == '\n' )
5326 {
5327 if ( index + 1 < buf.size() )
5328 {
5329 if ( buf[index + 1] == '+' || buf[index + 1] == '-' )
5330 {
5331 // skip adding dot
5332 ++index;
5333 continue;
5334 }
5335 }
5336 entry.desc += "\x1E ";
5337 }
5338 ++index;
5339 }
5340 }
5341 entry.internal_name = itr->name.GetString();
5342 if ( itr->value["img_path"].IsArray() )
5343 {
5344 for ( auto arr = itr->value["img_path"].Begin();
5345 arr != itr->value["img_path"].End(); ++arr )
5346 {
5347 entry.imgPathVariations.push_back(arr->GetString());
5348 }
5349 }
5350 else
5351 {
5352 entry.imgPath = itr->value["img_path"].GetString();
5353 }
5354 if ( itr->value["img_from_spell_id"].IsArray() )
5355 {
5356 for ( auto arr = itr->value["img_from_spell_id"].Begin();
5357 arr != itr->value["img_from_spell_id"].End(); ++arr )
5358 {
5359 entry.useSpellIDForImgVariations.push_back(arr->GetInt());
5360 }
5361 }
5362 else
5363 {
5364 entry.useSpellIDForImg = itr->value["img_from_spell_id"].GetInt();
5365 }
5366 entry.sustainedSpellID = -1;
5367 if ( itr->value.HasMember("use_entry_for_sustained_spell") )
5368 {
5369 entry.sustainedSpellID = itr->value["use_entry_for_sustained_spell"].GetInt();
5370 }
5371 entry.neverDisplay = false;
5372 if ( itr->value.HasMember("never_display") )
5373 {
5374 entry.neverDisplay = itr->value["never_display"].GetBool();
5375 }
5376 entry.tooltipWidth = defaultTooltipWidth;
5377 if ( itr->value.HasMember("tooltip_width") )
5378 {
5379 entry.tooltipWidth = itr->value["tooltip_width"].GetInt();
5380 }
5381 }
5382 }
5383 printlog("[JSON]: Successfully read json file %s", inputPath.c_str());
5384 }
5385 }
5386 }
5387}
5388
5389int StatusEffectQueue_t::getBaseEffectPosX()
5390{
5391 if ( players[player]->bUseCompactGUIHeight() )
5392 {
5393 return statusEffectFrame->getSize().w / 2 - 100;
5394 }
5395 return statusEffectFrame->getSize().w / 2 - 100;
5396}
5397int StatusEffectQueue_t::getBaseEffectPosY()
5398{
5399 if ( players[player]->bUseCompactGUIHeight() )
5400 {
5401 if ( players[player]->bUseCompactGUIWidth() && !players[player]->hotbar.useHotbarFaceMenu )
5402 {
5403 return statusEffectFrame->getSize().h / 2 + 32;
5404 }
5405 return statusEffectFrame->getSize().h / 2;
5406 }
5407 return statusEffectFrame->getSize().h / 2 - 50;
5408}
5409
5410int StatusEffectQueueEntry_t::getEffectSpriteNormalWidth()
5411{
5412 if ( effect == StatusEffectQueue_t::kEffectBread
5413 || effect == StatusEffectQueue_t::kEffectBloodHunger )
5414 {
5415 return 76;
5416 }
5417 else if ( effect == StatusEffectQueue_t::kEffectAutomatonHunger )
5418 {
5419 return 64;
5420 }
5421 return 32;
5422}
5423int StatusEffectQueueEntry_t::getEffectSpriteNormalHeight()
5424{
5425 if ( effect == StatusEffectQueue_t::kEffectBread
5426 || effect == StatusEffectQueue_t::kEffectBloodHunger )
5427 {
5428 return 60;
5429 }
5430 else if ( effect == StatusEffectQueue_t::kEffectAutomatonHunger )
5431 {
5432 return 64;
5433 }
5434 return 32;
5435}
5436
5437int getStatusEffectMovementAmount(int player)
5438{
5439 int movementAmount = 50;
5440 if ( players[player]->bUseCompactGUIHeight() )
5441 {
5442 movementAmount = 25;
5443 }
5444 return movementAmount;
5445}
5446
5447real_t StatusEffectQueueEntry_t::getStatusEffectLargestScaling(int player)
5448{
5449 if ( effect == StatusEffectQueue_t::kEffectBread
5450 || effect == StatusEffectQueue_t::kEffectBloodHunger
5451 || effect == StatusEffectQueue_t::kEffectAutomatonHunger )
5452 {
5453 return 2.0;
5454 }
5455 return 3.0;
5456}
5457
5458real_t StatusEffectQueueEntry_t::getStatusEffectMidScaling(int player)
5459{
5460 if ( effect == StatusEffectQueue_t::kEffectBread
5461 || effect == StatusEffectQueue_t::kEffectBloodHunger
5462 || effect == StatusEffectQueue_t::kEffectAutomatonHunger )
5463 {
5464 return 1.5;
5465 }
5466 return 2.0;
5467}
5468
5469void StatusEffectQueueEntry_t::animateNotification(int player)
5470{
5471 auto& statusEffectQueue = StatusEffectQueue[player];
5472 real_t animspeed = 5.0 / kStatusEffectQueueAnimSpeedMult;
5473 const int movementAmount = getStatusEffectMovementAmount(player);
5474 switch ( notificationState )
5475 {
5476 case STATE_1:
5477 if ( notificationStateInit == STATE_1 )
5478 {
5479 notificationStateInit = STATE_2;
5480 setAnimatePosition(
5481 statusEffectQueue.getBaseEffectPosX() - movementAmount,
5482 statusEffectQueue.getBaseEffectPosY() - movementAmount,
5483 getEffectSpriteNormalWidth(), getEffectSpriteNormalHeight());
5484 }
5485 if ( animateX >= 1.0 )
5486 {
5487 notificationState = STATE_2;
5488 }
5489 animspeed *= 2.0;
5490 break;
5491 case STATE_2:
5492 if ( notificationStateInit == STATE_2 )
5493 {
5494 notificationStateInit = STATE_3;
5495 setAnimatePosition(
5496 statusEffectQueue.getBaseEffectPosX() - movementAmount - (getStatusEffectLargestScaling(player) - 1.0) * getEffectSpriteNormalWidth() / 2,
5497 statusEffectQueue.getBaseEffectPosY() - movementAmount - (getStatusEffectLargestScaling(player) - 1.0) * getEffectSpriteNormalHeight() / 2,
5498 getEffectSpriteNormalWidth() * getStatusEffectLargestScaling(player),
5499 getEffectSpriteNormalHeight() * getStatusEffectLargestScaling(player));
5500 }
5501 if ( animateX >= 1.0 )
5502 {
5503 notificationState = STATE_3;
5504 }
5505 animspeed *= 4.0;
5506 break;
5507 case STATE_3:
5508 if ( notificationStateInit == STATE_3 )
5509 {
5510 notificationStateInit = STATE_4;
5511 setAnimatePosition(
5512 statusEffectQueue.getBaseEffectPosX() - movementAmount - (getStatusEffectMidScaling(player) - 1.0) * getEffectSpriteNormalWidth() / 2,
5513 statusEffectQueue.getBaseEffectPosY() - movementAmount - (getStatusEffectMidScaling(player) - 1.0) * getEffectSpriteNormalHeight() / 2,
5514 getEffectSpriteNormalWidth() * getStatusEffectMidScaling(player),
5515 getEffectSpriteNormalHeight() * getStatusEffectMidScaling(player));
5516 }
5517 if ( animateX >= 1.0 )
5518 {
5519 notificationState = STATE_4;
5520 }
5521 animspeed *= 4.0;
5522 break;
5523 case STATE_4:
5524 if ( notificationStateInit == STATE_4 )
5525 {
5526 notificationStateInit = STATE_END;
5527 setAnimatePosition(notificationTargetPosition.x,
5528 notificationTargetPosition.y,
5529 notificationTargetPosition.w,
5530 notificationTargetPosition.h);
5531 }
5532 if ( notificationTargetPosition.x != animateSetpointX
5533 || notificationTargetPosition.y != animateSetpointY
5534 || notificationTargetPosition.w != animateSetpointW
5535 || notificationTargetPosition.h != animateSetpointH )
5536 {
5537 // re update this as our target moved.
5538 setAnimatePosition(notificationTargetPosition.x,
5539 notificationTargetPosition.y,
5540 notificationTargetPosition.w,
5541 notificationTargetPosition.h);
5542 }
5543 if ( animateX >= 1.0 )
5544 {
5545 notificationState = STATE_END;
5546 }
5547 animspeed *= 2.0;
5548 break;
5549 case STATE_END:
5550 return;
5551 default:
5552 break;
5553 }
5554
5555 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
5556 real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - animateX)) / (animspeed);
5557 real_t setpointDiffY = fpsScale * std::max(.1, (1.0 - animateY)) / (animspeed);
5558 real_t setpointDiffW = fpsScale * std::max(.1, (1.0 - animateW)) / (animspeed);
5559 real_t setpointDiffH = fpsScale * std::max(.1, (1.0 - animateH)) / (animspeed);
5560 animateX += setpointDiffX;
5561 animateY += setpointDiffY;
5562 animateX = std::min(1.0, animateX);
5563 animateY = std::min(1.0, animateY);
5564 animateW += setpointDiffW;
5565 animateH += setpointDiffH;
5566 animateW = std::min(1.0, animateW);
5567 animateH = std::min(1.0, animateH);
5568
5569 int destX = animateSetpointX - animateStartX;
5570 int destY = animateSetpointY - animateStartY;
5571 int destW = animateSetpointW - animateStartW;
5572 int destH = animateSetpointH - animateStartH;
5573
5574 pos.x = animateStartX + destX * animateX;
5575 pos.y = animateStartY + destY * animateY;
5576 pos.w = animateStartW + destW * animateW;
5577 pos.h = animateStartH + destH * animateH;
5578}
5579
5580void createStatusEffectQueue(const int player)
5581{
5582 auto& statusEffectQueue = StatusEffectQueue[player];
5583 if ( statusEffectQueue.statusEffectFrame )
5584 {
5585 return;
5586 }
5587 auto& hud_t = players[player]->hud;
5588 statusEffectQueue.statusEffectFrame = hud_t.hudFrame->addFrame("status effects");
5589 statusEffectQueue.statusEffectFrame->setHollow(true);
5590 statusEffectQueue.statusEffectFrame->setBorder(0);
5591 statusEffectQueue.statusEffectFrame->setOwner(player);
5592 statusEffectQueue.statusEffectFrame->setSize(SDL_Rect{ 0, 0, 0, 0 });
5593
5594 auto automatonHungerFrame = statusEffectQueue.statusEffectFrame->addFrame("automaton hunger notification");
5595 automatonHungerFrame->setHollow(true);
5596 automatonHungerFrame->setDisabled(true);
5597 automatonHungerFrame->setSize(SDL_Rect{ 0, 0, 64, 64 });
5598 auto automaton_flame_img = automatonHungerFrame->addImage(SDL_Rect{ 0, 0, 64, 64 }, 0xFFFFFFFF, "images/system/Hunger_boiler_fire.png", "flame");
5599
5600 auto notif_frame = statusEffectQueue.statusEffectFrame->addFrame("notification frame");
5601 notif_frame->setHollow(true);
5602 notif_frame->setDisabled(true);
5603 notif_frame->setSize(SDL_Rect{ 0, 0, 0, 0 });
5604 auto notif = notif_frame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "images/system/white.png", "notification img");
5605 notif->disabled = true;
5606 auto notif_txt = notif_frame->addField("notification txt", 128);
5607 //notif_txt->setFont("fonts/pixel_maz_multiline.ttf#16#2");
5608 notif_txt->setFont("fonts/pixelmix.ttf#16#2");
5609 notif_txt->setText("");
5610 notif_txt->setDisabled(true);
5611 notif_txt->setColor(makeColor(255, 255, 255, 255));
5612 notif_txt->setVJustify(Field::justify_t::CENTER);
5613 notif_txt->setHJustify(Field::justify_t::CENTER);
5614
5615 auto innerFrame = statusEffectQueue.statusEffectFrame->addFrame("effects");
5616 innerFrame->setHollow(true);
5617}
5618
5619const int breadStatusEffectHeight = 60;
5620
5621std::string& StatusEffectQueue_t::EffectDefinitionEntry_t::getName(int variation)
5622{
5623 if ( variation >= 0 )
5624 {
5625 return nameVariations[std::min(variation, (int)nameVariations.size() - 1)];
5626 }
5627 return name;
5628}
5629
5630std::string& StatusEffectQueue_t::EffectDefinitionEntry_t::getDesc(int variation)
5631{
5632 if ( variation >= 0 )
5633 {
5634 return descVariations[std::min(variation, (int)descVariations.size() - 1)];
5635 }
5636 return desc;
5637}
5638
5639void StatusEffectQueue_t::createStatusEffectTooltip()
5640{
5641 auto& tooltipFrame = statusEffectTooltipFrame;
5642 if ( tooltipFrame )
5643 {
5644 return;
5645 }
5646 char name[32];
5647 snprintf(name, sizeof(name), "player statusfx tooltip %d", player);
5648 tooltipFrame = gameUIFrame[player]->addFrame(name);
5649 tooltipFrame->setHollow(true);
5650 tooltipFrame->setDisabled(true);
5651 tooltipFrame->setInheritParentFrameOpacity(false);
5652 tooltipFrame->setBorder(0);
5653 tooltipFrame->setOwner(player);
5654 tooltipFrame->setSize(SDL_Rect{ 0, 0, 0, 0 });
5655
5656 {
5657 Uint32 color = makeColor(255, 255, 255, 255);
5658 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
5659 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TL_Blue_00.png", skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
5660 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
5661 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TR_Blue_00.png", skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
5662 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
5663 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_T_Blue_00.png", skillsheetEffectBackgroundImages[TOP].c_str());
5664 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
5665 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_L_00.png", skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str());
5666 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
5667 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_R_00.png", skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str());
5668 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
5669 makeColor(22, 24, 29, 255), "images/system/white.png", skillsheetEffectBackgroundImages[MIDDLE].c_str());
5670 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
5671 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_BL_00.png", skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str());
5672 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
5673 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_BR_00.png", skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str());
5674 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
5675 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_B_00.png", skillsheetEffectBackgroundImages[BOTTOM].c_str());
5676 imageSetWidthHeight9x9(tooltipFrame, skillsheetEffectBackgroundImages);
5677
5678 auto heading_txt = tooltipFrame->addField("heading txt", 128);
5679 heading_txt->setFont("fonts/pixel_maz_multiline.ttf#16#2");
5680 heading_txt->setText("");
5681 heading_txt->setColor(makeColor(255, 255, 255, 255));
5682 heading_txt->setVJustify(Field::justify_t::CENTER);
5683 heading_txt->setHJustify(Field::justify_t::LEFT);
5684
5685 auto desc_txt = tooltipFrame->addField("desc txt", 1024);
5686 desc_txt->setFont("fonts/pixel_maz_multiline.ttf#16#2");
5687 desc_txt->setText("");
5688 desc_txt->setColor(makeColor(0, 192, 255, 255));
5689 desc_txt->setVJustify(Field::justify_t::LEFT);
5690 desc_txt->setHJustify(Field::justify_t::LEFT);
5691 }
5692}
5693
5694void Player::HUD_t::updateStatusEffectTooltip()
5695{
5696 StatusEffectQueue[player.playernum].createStatusEffectTooltip();
5697}
5698
5699void Player::HUD_t::closeStatusFxWindow()
5700{
5701 bool wasActive = statusFxFocusedWindowActive;
5702 statusFxFocusedWindowActive = false;
5703
5704 StatusEffectQueue[player.playernum].focusedWindowAnim = 0.0;
5705 if ( statusEffectFocusedWindow )
5706 {
5707 statusEffectFocusedWindow->setDisabled(true);
5708 }
5709 if ( StatusEffectQueue[player.playernum].statusEffectFrame )
5710 {
5711 StatusEffectQueue[player.playernum].statusEffectFrame->setDisabled(false);
5712 }
5713
5714 if ( wasActive )
5715 {
5716 player.GUI.returnToPreviousActiveModule();
5717 }
5718}
5719
5720void Player::HUD_t::updateStatusEffectFocusedWindow()
5721{
5722 if ( !statusEffectFocusedWindow )
5723 {
5724 char name[32];
5725 snprintf(name, sizeof(name), "player statusfx window %d", player.playernum);
5726 statusEffectFocusedWindow = gameUIFrame[player.playernum]->addFrame(name);
5727 statusEffectFocusedWindow->setHollow(false);
5728 statusEffectFocusedWindow->setBorder(0);
5729 statusEffectFocusedWindow->setOwner(player.playernum);
5730 statusEffectFocusedWindow->setSize(SDL_Rect{ 0, 0, 0, 0 });
5731 statusEffectFocusedWindow->setDisabled(true);
5732
5733 /*auto img = statusEffectFocusedWindow->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
5734 "*#images/ui/HUD/statusfx/background_panel.png", "background");
5735 img->disabled = false;*/
5736
5737 auto backgroundFrame = statusEffectFocusedWindow->addFrame("background frame");
5738 {
5739 auto heading_txt = backgroundFrame->addField("heading txt", 128);
5740 heading_txt->setFont("fonts/pixel_maz_multiline.ttf#16#2");
5741 heading_txt->setText(Language::get(4345));
5742 heading_txt->setColor(makeColor(255, 255, 255, 255));
5743 heading_txt->setVJustify(Field::justify_t::CENTER);
5744 heading_txt->setHJustify(Field::justify_t::CENTER);
5745 heading_txt->setSize(SDL_Rect{ 16, 2, 256, 30 });
5746 heading_txt->setColor(hudColors.characterSheetNeutral);
5747
5748 backgroundFrame->addImage(SDL_Rect{ 24, 0, 0, 30 },
5749 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/HoverFxMenu_T03.png", skillsheetEffectBackgroundImages[TOP].c_str());
5750 backgroundFrame->addImage(SDL_Rect{ 0, 0, 24, 30 },
5751 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/HoverFxMenu_TL03.png", skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
5752 backgroundFrame->addImage(SDL_Rect{ 0, 0, 24, 30 },
5753 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/HoverFxMenu_TR03.png", skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
5754 backgroundFrame->addImage(SDL_Rect{ 24, 30, 0, 12 },
5755 makeColor(22, 24, 29, 255),
5756 "*#images/ui/Inventory/tooltips/HoverFxMenu_C03.png", skillsheetEffectBackgroundImages[MIDDLE].c_str());
5757 auto ml = backgroundFrame->addImage(SDL_Rect{ 0, 30, 24, 12 },
5758 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/HoverFxMenu_L03.png", skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str());
5759 ml->tiled = true;
5760 auto mr = backgroundFrame->addImage(SDL_Rect{ 0, 30, 24, 12 },
5761 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/HoverFxMenu_R03.png", skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str());
5762 mr->tiled = true;
5763
5764 backgroundFrame->addImage(SDL_Rect{ 24, 96, 0, 14 },
5765 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/HoverFxMenu_B03.png", skillsheetEffectBackgroundImages[BOTTOM].c_str());
5766 backgroundFrame->addImage(SDL_Rect{ 0, 96, 24, 14 },
5767 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/HoverFxMenu_BL03.png", skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str());
5768 backgroundFrame->addImage(SDL_Rect{ 0, 96, 24, 14 },
5769 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/HoverFxMenu_BR03.png", skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str());
5770
5771 auto dismiss = backgroundFrame->addButton("dismiss button");
5772 dismiss->setSize(SDL_Rect{ 0, 0, 90, 34 });
5773 dismiss->setColor(makeColor(255, 255, 255, 255));
5774 dismiss->setHighlightColor(makeColor(255, 255, 255, 255));
5775 dismiss->setBackground("*#images/ui/Inventory/chests/Button_TakeAll_00.png");
5776 dismiss->setBackgroundHighlighted("*#images/ui/Inventory/chests/Button_TakeAllHigh_00.png");
5777 dismiss->setBackgroundActivated("*#images/ui/Inventory/chests/Button_TakeAllPress_00.png");
5778 dismiss->setText(Language::get(5835));
5779 dismiss->setFont(smallfont_outline);
5780 dismiss->setTextHighlightColor(makeColor(201, 162, 100, 255));
5781 dismiss->setDisabled(true);
5782 dismiss->setHideGlyphs(true);
5783 dismiss->setHideKeyboardGlyphs(true);
5784 dismiss->setMenuConfirmControlType(0);
5785 dismiss->setHideSelectors(true);
5786 dismiss->setOntop(true);
5787 dismiss->setCallback([](Button& button) {
5788 players[button.getOwner()]->hud.closeStatusFxWindow();
5789 Player::soundCancel();
5790 });
5791
5792 auto noEffectTxt = backgroundFrame->addField("no effect txt", 128);
5793 noEffectTxt->setFont(smallfont_outline);
5794 noEffectTxt->setText(Language::get(4346));
5795 noEffectTxt->setDisabled(true);
5796 noEffectTxt->setColor(makeColor(255, 255, 255, 255));
5797 noEffectTxt->setVJustify(Field::justify_t::CENTER);
5798 noEffectTxt->setHJustify(Field::justify_t::CENTER);
5799 }
5800
5801 auto automatonHungerFrame = statusEffectFocusedWindow->addFrame("automaton hunger notification");
5802 automatonHungerFrame->setHollow(true);
5803 automatonHungerFrame->setDisabled(true);
5804 automatonHungerFrame->setSize(SDL_Rect{ 0, 0, 64, 64 });
5805 auto automaton_flame_img = automatonHungerFrame->addImage(SDL_Rect{ 0, 0, 64, 64 }, 0xFFFFFFFF, "images/system/Hunger_boiler_fire.png", "flame");
5806
5807 auto notif_frame = statusEffectFocusedWindow->addFrame("notification frame");
5808 notif_frame->setHollow(true);
5809 notif_frame->setDisabled(true);
5810 notif_frame->setSize(SDL_Rect{ 0, 0, 0, 0 });
5811 auto notif = notif_frame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "images/system/white.png", "notification img");
5812 notif->disabled = true;
5813 auto notif_txt = notif_frame->addField("notification txt", 128);
5814 //notif_txt->setFont("fonts/pixel_maz_multiline.ttf#16#2");
5815 notif_txt->setFont("fonts/pixelmix.ttf#16#2");
5816 notif_txt->setText("");
5817 notif_txt->setDisabled(true);
5818 notif_txt->setColor(makeColor(255, 255, 255, 255));
5819 notif_txt->setVJustify(Field::justify_t::CENTER);
5820 notif_txt->setHJustify(Field::justify_t::CENTER);
5821
5822 auto innerFrame = statusEffectFocusedWindow->addFrame("effects");
5823 innerFrame->setHollow(true);
5824 }
5825
5826 auto& animBackground = StatusEffectQueue[player.playernum].focusedWindowAnim;
5827 if ( player.shootmode
5828 || player.inventory_mode != INVENTORY_MODE_ITEM
5829 || (player.inventoryUI.bCompactView && compactLayoutMode != COMPACT_LAYOUT_INVENTORY) )
5830 {
5831 closeStatusFxWindow();
5832 return;
5833 }
5834 /*if ( enableDebugKeys && keystatus[SDLK_g] )
5835 {
5836 statusFxFocusedWindowActive = true;
5837 }*/
5838 statusEffectFocusedWindow->setDisabled(true);
5839 if ( StatusEffectQueue[player.playernum].statusEffectFrame && statusFxFocusedWindowActive )
5840 {
5841 statusEffectFocusedWindow->setDisabled(false);
5842 StatusEffectQueue[player.playernum].statusEffectFrame->setDisabled(true);
5843
5844 Frame* srcFrame = StatusEffectQueue[player.playernum].statusEffectFrame;
5845 Frame* destFrame = statusEffectFocusedWindow;
5846 destFrame->setSize(SDL_Rect{ player.camera_virtualx1(),
5847 player.camera_virtualy1(),
5848 player.camera_virtualWidth(),
5849 player.camera_virtualHeight() });
5850
5851 const int offsetH = 6;
5852
5853 SDL_Rect destPos = destFrame->getSize();
5854 destPos.h += offsetH;
5855 destFrame->setSize(destPos);
5856
5857
5858 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
5859 real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - animBackground)) / (2.5);
5860
5861 animBackground += setpointDiffX;
5862 animBackground = std::min(1.0, animBackground);
5863
5864 size_t f = 0;
5865 for ( auto srcf : srcFrame->getFrames() )
5866 {
5867 Frame* destf = destFrame->getFrames()[f];
5868 if ( !strcmp(destf->getName(), "background frame") )
5869 {
5870 ++f;
5871 destf = destFrame->getFrames()[f];
5872 }
5873
5874 SDL_Rect srcSize = srcf->getSize();
5875 srcSize.x += srcFrame->getSize().x;
5876 srcSize.y += srcFrame->getSize().y;
5877 destf->setSize(srcSize);
5878 destf->setDisabled(srcf->isDisabled());
5879
5880 if ( !strcmp(srcf->getName(), "effects") )
5881 {
5882 int numFrameImages = srcf->getImages().size();
5883 while ( destf->getImages().size() < numFrameImages )
5884 {
5885 auto img = destf->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "inner img");
5886 img->disabled = true;
5887 }
5888 while ( destf->getImages().size() > numFrameImages )
5889 {
5890 destf->getImages().erase(destf->getImages().begin());
5891 numFrameImages = destf->getImages().size();
5892 }
5893 }
5894
5895 size_t i = 0;
5896 for ( auto srcImg : srcf->getImages() )
5897 {
5898 auto destImg = destf->getImages()[i];
5899 destImg->pos = srcImg->pos;
5900 destImg->path = srcImg->path;
5901 destImg->disabled = srcImg->disabled;
5902 ++i;
5903 }
5904
5905 size_t t = 0;
5906 for ( auto srcTxt : srcf->getFields() )
5907 {
5908 auto destTxt = destf->getFields()[t];
5909 destTxt->setText(srcTxt->getText());
5910 destTxt->setSize(srcTxt->getSize());
5911 destTxt->setDisabled(srcTxt->isDisabled());
5912 ++t;
5913 }
5914 ++f;
5915 }
5916
5917 auto& effectQueue = StatusEffectQueue[player.playernum].effectQueue;
5918 auto bgFrame = destFrame->findFrame("background frame");
5919 SDL_Rect bgPos{ 0, 0,
5920 std::max(126, StatusEffectQueue[player.playernum].effectsBoundingBox.w),
5921 std::max(32, StatusEffectQueue[player.playernum].effectsBoundingBox.y -
5922 StatusEffectQueue[player.playernum].effectsBoundingBox.h)};
5923 if ( effectQueue.size() == 0 )
5924 {
5925 bgPos.w = std::max(bgPos.w, 126 + 18);
5926 bgPos.h = std::max(bgPos.h, 32 + 24);
5927 }
5928 {
5929 bgPos.h += 42;
5930 bgPos.w += 28;
5931 int startx = std::max(4, player.hud.hpFrame->getSize().x - 4);
5932 bgPos.x = -(bgPos.w) + (bgPos.w + startx) * animBackground;
5933 //bgPos.x = startx;
5934 bgFrame->setInheritParentFrameOpacity(false);
5935 bgFrame->setOpacity(animBackground * 100.0);
5936 bgFrame->setSize(bgPos);
5937
5938 int maxHeight = bgPos.h;
5939 int interimHeight = 0;
5940 {
5941 auto tl = bgFrame->findImage(skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
5942 auto tmid = bgFrame->findImage(skillsheetEffectBackgroundImages[TOP].c_str());
5943 tmid->pos.w = bgPos.w - tl->pos.w * 2;
5944 auto tr = bgFrame->findImage(skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
5945 tr->pos.x = tmid->pos.x + tmid->pos.w;
5946
5947 interimHeight = tmid->pos.y + tmid->pos.h;
5948 bgPos.w = tr->pos.x + tr->pos.w;
5949 }
5950 {
5951 const int middleOffsetY = 12;
5952 auto ml = bgFrame->findImage(skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str());
5953 ml->pos.h = maxHeight - interimHeight - middleOffsetY;
5954 auto mmid = bgFrame->findImage(skillsheetEffectBackgroundImages[MIDDLE].c_str());
5955 mmid->pos.h = ml->pos.h;
5956 mmid->pos.w = bgPos.w - (ml->pos.w) * 2;
5957 mmid->color = hudColors.itemContextMenuOptionImg;
5958 auto mr = bgFrame->findImage(skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str());
5959 mr->pos.h = ml->pos.h;
5960 mr->pos.x = mmid->pos.x + mmid->pos.w;
5961
5962 interimHeight = mmid->pos.y + mmid->pos.h;
5963 }
5964 {
5965 auto bl = bgFrame->findImage(skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str());
5966 bl->pos.y = interimHeight;
5967 auto bmid = bgFrame->findImage(skillsheetEffectBackgroundImages[BOTTOM].c_str());
5968 bmid->pos.y = interimHeight;
5969 bmid->pos.w = bgPos.w - bl->pos.w * 2;
5970 auto br = bgFrame->findImage(skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str());
5971 br->pos.y = interimHeight;
5972 br->pos.x = bmid->pos.x + bmid->pos.w;
5973
5974 bgPos.h = br->pos.y + br->pos.h;
5975 }
5976 bgPos.y = srcFrame->getSize().y + (srcFrame->getSize().h - bgPos.h + offsetH);
5977 bgPos.y += 4;
5978 bgFrame->setSize(bgPos);
5979 }
5980
5981 auto dismiss = bgFrame->findButton("dismiss button");
5982 dismiss->setDisabled(true);
5983 auto noEffectTxt = bgFrame->findField("no effect txt");
5984 noEffectTxt->setDisabled(true);
5985 if ( effectQueue.size() == 0 )
5986 {
5987 dismiss->setDisabled(false);
5988 noEffectTxt->setDisabled(false);
5989 }
5990 if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
5991 {
5992 if ( Input::inputs[player.playernum].binaryToggle("MenuLeftClick") && inputs.bPlayerUsingKeyboardControl(player.playernum) )
5993 {
5994 Input::inputs[player.playernum].consumeBinaryToggle("MenuLeftClick");
5995 if ( !bgFrame->capturesMouse() )
5996 {
5997 closeStatusFxWindow();
5998 Player::soundCancel();
5999 return;
6000 }
6001 }
6002 }
6003 else
6004 {
6005 if ( StatusEffectQueue[player.playernum].statusEffectTooltipFrame )
6006 {
6007 SDL_Rect tooltipPos = StatusEffectQueue[player.playernum].statusEffectTooltipFrame->getSize();
6008
6009 SDL_Rect tooltipAbsoluteSize = StatusEffectQueue[player.playernum].statusEffectTooltipFrame->getAbsoluteSize();
6010 SDL_Rect bgAbsoluteSize = bgFrame->getAbsoluteSize();
6011
6012 int positionDiffX = tooltipAbsoluteSize.x;
6013 positionDiffX -= (bgAbsoluteSize.x + bgAbsoluteSize.w + 4);
6014 int positionDiffY = tooltipAbsoluteSize.y;
6015 positionDiffY -= (bgAbsoluteSize.y);
6016
6017 tooltipPos.x = std::max(tooltipPos.x, tooltipPos.x - positionDiffX);
6018 tooltipPos.y -= positionDiffY;
6019 int overflow = tooltipPos.y + tooltipPos.h - player.camera_virtualy2();
6020 if ( overflow > 0 )
6021 {
6022 tooltipPos.y -= overflow;
6023 }
6024
6025 StatusEffectQueue[player.playernum].statusEffectTooltipFrame->setSize(tooltipPos);
6026 }
6027 }
6028
6029 if ( inputs.hasController(player.playernum) )
6030 {
6031 if ( Input::inputs[player.playernum].binaryToggle("MenuCancel")
6032 || Input::inputs[player.playernum].binaryToggle("MenuConfirm") )
6033 {
6034 Input::inputs[player.playernum].consumeBinaryToggle("MenuCancel");
6035 Input::inputs[player.playernum].consumeBinaryToggle("MenuConfirm");
6036 closeStatusFxWindow();
6037 Player::soundCancel();
6038 return;
6039 }
6040 }
6041
6042 if ( player.GUI.bModuleAccessibleWithMouse(Player::GUI_t::MODULE_STATUS_EFFECTS) )
6043 {
6044 if ( (dismiss->isHighlighted() || !inputs.getVirtualMouse(player.playernum)->draw_cursor )
6045 && !dismiss->isDisabled() && animBackground >= 0.99 )
6046 {
6047 player.GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_STATUS_EFFECTS);
6048 if ( player.GUI.activeModule != Player::GUI_t::MODULE_STATUS_EFFECTS )
6049 {
6050 player.GUI.activateModule(Player::GUI_t::MODULE_STATUS_EFFECTS);
6051 }
6052 SDL_Rect pos = dismiss->getAbsoluteSize();
6053 // make sure to adjust absolute size to camera viewport
6054 pos.x -= player.camera_virtualx1();
6055 pos.y -= player.camera_virtualy1();
6056
6057 player.hud.setCursorDisabled(false);
6058 player.hud.updateCursorAnimation(pos.x - 1, pos.y - 1, pos.w, pos.h, inputs.getVirtualMouse(player.playernum)->draw_cursor);
6059 }
6060 }
6061
6062 {
6063 SDL_Rect btnSize = dismiss->getSize();
6064 dismiss->setSize(SDL_Rect{ bgPos.w / 2 - btnSize.w / 2,
6065 bgPos.h - btnSize.h - 8, btnSize.w, btnSize.h });
6066 SDL_Rect txtSize = noEffectTxt->getSize();
6067 txtSize.w = bgPos.w;
6068 txtSize.h = 24;
6069 txtSize.x = 0;
6070 txtSize.y = btnSize.y - 24;
6071 if ( txtSize.y % 2 == 1 )
6072 {
6073 txtSize.y--;
6074 }
6075
6076 auto heading_txt = bgFrame->findField("heading txt");
6077 SDL_Rect headerTxtSize = heading_txt->getSize();
6078 headerTxtSize.w = bgPos.w;
6079 headerTxtSize.x = 0;
6080 heading_txt->setSize(headerTxtSize);
6081
6082 noEffectTxt->setSize(txtSize);
6083 dismiss->setInvisible(dismiss->isDisabled());
6084 }
6085 }
6086 else
6087 {
6088 closeStatusFxWindow();
6089 return;
6090 }
6091
6092 if ( player.hud.hudFrame && player.hud.hudFrame->isDisabled() )
6093 {
6094 closeStatusFxWindow();
6095 return;
6096 }
6097 return;
6098 //Frame* fxFrame = nullptr;
6099 //if ( !statusEffectFocusedWindow )
6100 //{
6101 // char name[32];
6102 // snprintf(name, sizeof(name), "player statusfx window %d", player.playernum);
6103 // statusEffectFocusedWindow = gameUIFrame[player.playernum]->addFrame(name);
6104 // Frame* frame = statusEffectFocusedWindow;
6105 // frame->setHollow(false);
6106 // frame->setDisabled(true);
6107 // frame->setInheritParentFrameOpacity(true);
6108 // frame->setBorder(0);
6109 // frame->setOwner(player.playernum);
6110 // frame->setSize(SDL_Rect{ 0, 0, 0, 0 });
6111
6112 // {
6113 // Uint32 color = makeColor(255, 255, 255, 255);
6114 // frame->addImage(SDL_Rect{ 0, 0, 6, 6 },
6115 // color, "*#images/ui/Inventory/tooltips/HoverItemMenu_TL03.png", skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
6116 // frame->addImage(SDL_Rect{ 0, 0, 6, 6 },
6117 // color, "*#images/ui/Inventory/tooltips/HoverItemMenu_TR03.png", skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
6118 // frame->addImage(SDL_Rect{ 0, 0, 6, 6 },
6119 // color, "*#images/ui/Inventory/tooltips/HoverItemMenu_T03.png", skillsheetEffectBackgroundImages[TOP].c_str());
6120 // frame->addImage(SDL_Rect{ 0, 0, 6, 6 },
6121 // color, "*#images/ui/Inventory/tooltips/HoverItemMenu_L03.png", skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str());
6122 // frame->addImage(SDL_Rect{ 0, 0, 6, 6 },
6123 // color, "*#images/ui/Inventory/tooltips/HoverItemMenu_R03.png", skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str());
6124 // frame->addImage(SDL_Rect{ 0, 0, 6, 6 },
6125 // makeColor(22, 24, 29, 255), "*#images/ui/Inventory/tooltips/HoverItemMenu_C03.png", skillsheetEffectBackgroundImages[MIDDLE].c_str());
6126 // frame->addImage(SDL_Rect{ 0, 0, 6, 6 },
6127 // color, "*#images/ui/Inventory/tooltips/HoverItemMenu_BL03.png", skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str());
6128 // frame->addImage(SDL_Rect{ 0, 0, 6, 6 },
6129 // color, "*#images/ui/Inventory/tooltips/HoverItemMenu_BR03.png", skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str());
6130 // frame->addImage(SDL_Rect{ 0, 0, 6, 6 },
6131 // color, "*#images/ui/Inventory/tooltips/HoverItemMenu_B03.png", skillsheetEffectBackgroundImages[BOTTOM].c_str());
6132 // imageSetWidthHeight9x9(frame, skillsheetEffectBackgroundImages);
6133
6134 // auto heading_txt = frame->addField("heading txt", 128);
6135 // heading_txt->setFont("fonts/pixel_maz_multiline.ttf#16#2");
6136 // heading_txt->setText("Status Effects");
6137 // heading_txt->setColor(makeColor(255, 255, 255, 255));
6138 // heading_txt->setVJustify(Field::justify_t::CENTER);
6139 // heading_txt->setHJustify(Field::justify_t::LEFT);
6140 // /*
6141 // auto desc_txt = tooltipFrame->addField("desc txt", 1024);
6142 // desc_txt->setFont("fonts/pixel_maz_multiline.ttf#16#2");
6143 // desc_txt->setText("");
6144 // desc_txt->setColor(makeColor(0, 192, 255, 255));
6145 // desc_txt->setVJustify(Field::justify_t::LEFT);
6146 // desc_txt->setHJustify(Field::justify_t::LEFT);*/
6147 // }
6148
6149 // auto automatonBgFrame = frame->addFrame("automaton bg");
6150 // automatonBgFrame->setSize(SDL_Rect{ 0, 0, 64, 64 });
6151 // automatonBgFrame->setDisabled(true);
6152 // automatonBgFrame->addImage(SDL_Rect{ 0, 0, 64, 64 }, 0xFFFFFFFF, "", "flame");
6153
6154 // fxFrame = frame->addFrame("effects");
6155 //}
6156 //else
6157 //{
6158 // fxFrame = statusEffectFocusedWindow->findFrame("effects");
6159 //}
6160
6161 //bool rebuildWindow = true;
6162 //if ( keystatus[SDLK_g] )
6163 //{
6164 // keystatus[SDLK_g] = 0;
6165 // if ( statusEffectFocusedWindow->isDisabled() )
6166 // {
6167 // statusEffectFocusedWindow->setDisabled(false);
6168 // rebuildWindow = true;
6169 // }
6170 // else
6171 // {
6172 // statusEffectFocusedWindow->setDisabled(true);
6173 // }
6174 //}
6175
6176 //if ( rebuildWindow && StatusEffectQueue[player.playernum].statusEffectFrame )
6177 //{
6178 // statusEffectFocusedWindow->setDisabled(false);
6179 // auto& effectQueue = StatusEffectQueue[player.playernum].effectQueue;
6180 // auto& notificationQueue = StatusEffectQueue[player.playernum].notificationQueue;
6181 // auto statusFx = StatusEffectQueue[player.playernum].statusEffectFrame->findFrame("effects");
6182
6183 // statusEffectFocusedWindow->setSize(statusFx->getSize());
6184 // fxFrame->setSize(statusFx->getSize());
6185
6186 // int numFrameImages = fxFrame->getImages().size();
6187 // while ( effectQueue.size() > numFrameImages )
6188 // {
6189 // auto img = fxFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "inner img");
6190 // img->disabled = true;
6191 // numFrameImages = fxFrame->getImages().size();
6192 // }
6193 // while ( effectQueue.size() < numFrameImages )
6194 // {
6195 // fxFrame->getImages().erase(fxFrame->getImages().begin());
6196 // numFrameImages = fxFrame->getImages().size();
6197 // }
6198 // auto& frameImages = fxFrame->getImages();
6199 // for ( auto img : frameImages )
6200 // {
6201 // img->disabled = true;
6202 // }
6203
6204 // auto automatonHungerFrame = statusEffectFocusedWindow->findFrame("automaton bg");
6205 // auto flameImg = automatonHungerFrame->findImage("flame");
6206 // automatonHungerFrame->setDisabled(true);
6207 // auto frameImagesIterator = frameImages.begin();
6208 // auto srcFrameImagesIterator = statusFx->getImages().begin();
6209 // size_t index = 0;
6210 // SDL_Rect windowSizeLimitMin{ 0, 0, 0, 0 };
6211 // SDL_Rect windowSizeLimitMax{ 0, 0, 0, 0 };
6212 // for ( auto it = effectQueue.rbegin(); it != effectQueue.rend(); )
6213 // {
6214 // auto& q = (*it);
6215 // Frame::image_t* frameImg = nullptr;
6216 // Frame::image_t* srcFrameImg = nullptr;
6217 // if ( frameImagesIterator != frameImages.end() )
6218 // {
6219 // frameImg = *frameImagesIterator;
6220 // }
6221 // if ( srcFrameImagesIterator != statusFx->getImages().end() )
6222 // {
6223 // srcFrameImg = *srcFrameImagesIterator;
6224 // }
6225
6226 // bool existsInNotifications = false;
6227 // for ( auto it2 = notificationQueue.begin(); it2 != notificationQueue.end(); ++it2 )
6228 // {
6229 // if ( (*it2).effect == q.effect )
6230 // {
6231 // existsInNotifications = true;
6232 // break;
6233 // }
6234 // }
6235
6236 // StatusEffectQueue[player.playernum].updateEntryImage(q, frameImg);
6237 // frameImg->pos.x = q.animateSetpointX;
6238 // frameImg->pos.y = q.animateSetpointY;
6239 // frameImg->disabled = (srcFrameImg && !existsInNotifications) ? srcFrameImg->disabled : false;
6240
6241 // if ( windowSizeLimitMin.x == 0 && index == 0 )
6242 // {
6243 // windowSizeLimitMin.x = frameImg->pos.x;
6244 // }
6245 // else
6246 // {
6247 // windowSizeLimitMin.x = std::min(frameImg->pos.x, windowSizeLimitMin.x);
6248 // }
6249 // if ( windowSizeLimitMin.y == 0 && index == 0 )
6250 // {
6251 // windowSizeLimitMin.y = frameImg->pos.y;
6252 // }
6253 // else
6254 // {
6255 // windowSizeLimitMin.y = std::min(frameImg->pos.y, windowSizeLimitMin.y);
6256 // }
6257 // windowSizeLimitMax.x = std::max(frameImg->pos.x + frameImg->pos.w, windowSizeLimitMax.x);
6258 // windowSizeLimitMax.y = std::max(frameImg->pos.y + frameImg->pos.h, windowSizeLimitMax.y);
6259
6260 // if ( q.effect == StatusEffectQueue_t::kEffectAutomatonHunger )
6261 // {
6262 // auto srcFrame = StatusEffectQueue[player.playernum].statusEffectFrame->findFrame("automaton hunger notification");
6263 // automatonHungerFrame->setDisabled(srcFrame->isDisabled());
6264
6265 // SDL_Rect pos = srcFrame->getSize();
6266 // pos.x = q.animateSetpointX;
6267 // pos.y = q.animateSetpointY;
6268
6269 // int heightDiff = q.getEffectSpriteNormalHeight() - pos.h;
6270 // pos.y += heightDiff;
6271
6272 // automatonHungerFrame->setSize(pos);
6273
6274 // auto srcImg = srcFrame->findImage("flame");
6275 // flameImg->pos = SDL_Rect{ 0, -heightDiff, q.getEffectSpriteNormalWidth(), q.getEffectSpriteNormalHeight() };
6276 // flameImg->path = srcImg->path;
6277 // flameImg->disabled = !existsInNotifications ? srcImg->disabled : false;
6278 // }
6279
6280 // ++it;
6281 // if ( frameImagesIterator != frameImages.end() )
6282 // {
6283 // ++frameImagesIterator;
6284 // }
6285 // if ( srcFrameImagesIterator != statusFx->getImages().end() )
6286 // {
6287 // ++srcFrameImagesIterator;
6288 // }
6289 // ++index;
6290 // }
6291
6292 // // rearrange icons to fit size
6293 // {
6294 // int borderX = 16;
6295 // int borderY = 32;
6296 // int bodyW = windowSizeLimitMax.x - windowSizeLimitMin.x;
6297 // int bodyH = windowSizeLimitMax.y - windowSizeLimitMin.y;
6298 // fxFrame->setSize(SDL_Rect{ borderX, borderY, bodyW, bodyH });
6299 // for ( auto img : frameImages )
6300 // {
6301 // img->pos.x -= (windowSizeLimitMin.x);
6302 // img->pos.y -= (windowSizeLimitMin.y);
6303 // }
6304 // SDL_Rect automatonHungerFramePos = automatonHungerFrame->getSize();
6305 // automatonHungerFramePos.x += borderX;
6306 // automatonHungerFramePos.y += borderY;
6307 // automatonHungerFramePos.x -= (windowSizeLimitMin.x);
6308 // automatonHungerFramePos.y -= (windowSizeLimitMin.y);
6309 // automatonHungerFrame->setSize(automatonHungerFramePos);
6310
6311 // SDL_Rect windowPos{ 0, 0, borderX * 2 + bodyW, borderY + bodyH + 16 };
6312 // statusEffectFocusedWindow->setSize(windowPos);
6313 // imageResizeToContainer9x9(statusEffectFocusedWindow, SDL_Rect{ 0, 0, windowPos.w, windowPos.h },
6314 // skillsheetEffectBackgroundImages);
6315
6316 // auto heading_txt = statusEffectFocusedWindow->findField("heading txt");
6317 // heading_txt->setSize(SDL_Rect{4, 4, windowPos.w - 4 * 2, 24});
6318 // }
6319 //}
6320}
6321
6322void StatusEffectQueue_t::animateStatusEffectTooltip(bool showTooltip)
6323{
6324 if ( !statusEffectTooltipFrame )
6325 {
6326 return;
6327 }
6328 auto tooltipFrame = statusEffectTooltipFrame;
6329 if ( static_cast<int>(tooltipFrame->getOpacity()) != tooltipOpacitySetpoint )
6330 {
6331 const real_t fpsScale = getFPSScale(144.0);
6332 if ( tooltipOpacitySetpoint == 0 )
6333 {
6334 if ( !inputs.getVirtualMouse(player)->draw_cursor )
6335 {
6336 tooltipOpacityAnimate = 0.0;
6337 }
6338 else
6339 {
6340 if ( ticks - tooltipDeselectedTick > 5 )
6341 {
6342 real_t factor = 10.0;
6343 real_t setpointDiff = fpsScale * std::max(.05, (tooltipOpacityAnimate)) / (factor);
6344 tooltipOpacityAnimate -= setpointDiff;
6345 tooltipOpacityAnimate = std::max(0.0, tooltipOpacityAnimate);
6346 }
6347 }
6348 }
6349 else
6350 {
6351 real_t setpointDiff = fpsScale * std::max(.05, (1.0 - tooltipOpacityAnimate)) / (1);
6352 tooltipOpacityAnimate += setpointDiff;
6353 tooltipOpacityAnimate = std::min(1.0, tooltipOpacityAnimate);
6354 }
6355 tooltipFrame->setOpacity(tooltipOpacityAnimate * 100);
6356 }
6357 else
6358 {
6359 tooltipFrame->setOpacity(tooltipOpacitySetpoint);
6360 }
6361
6362 if ( players[player]->hud.hudFrame && players[player]->hud.hudFrame->isDisabled() )
6363 {
6364 tooltipFrame->setDisabled(true);
6365 }
6366
6367 if ( tooltipFrame->isDisabled() || !showTooltip )
6368 {
6369 tooltipShowingEffectID = -1;
6370 tooltipShowingEffectVariable = -1;
6371 return;
6372 }
6373
6374 tooltipOpacitySetpoint = 0;
6375 tooltipOpacityAnimate = 1.0;
6376 tooltipFrame->setDisabled(false);
6377 tooltipFrame->setOpacity(100.0);
6378 tooltipDeselectedTick = ticks;
6379}
6380
6381bool StatusEffectQueue_t::doStatusEffectTooltip(StatusEffectQueueEntry_t& entry, SDL_Rect pos)
6382{
6383 auto tooltipFrame = statusEffectTooltipFrame;
6384 if ( !tooltipFrame )
6385 {
6386 return false;
6387 }
6388 auto tooltipHeader = tooltipFrame->findField("heading txt");
6389 tooltipHeader->setColor(StatusEffectDefinitions_t::tooltipHeadingColor);
6390 auto tooltipDesc = tooltipFrame->findField("desc txt");
6391 tooltipDesc->setColor(StatusEffectDefinitions_t::tooltipDescColor);
6392 int fontHeight = Font::get(tooltipDesc->getFont())->height(true);
6393 int tooltipInnerWidth = 200;
6394
6395 bool refreshTooltip = (tooltipShowingEffectID != entry.effect) || (tooltipShowingEffectVariable != entry.customVariable);
6396 if ( refreshTooltip )
6397 {
6398 if ( entry.effect >= StatusEffectQueue_t::kSpellEffectOffset )
6399 {
6400 int effectID = entry.effect - StatusEffectQueue_t::kSpellEffectOffset;
6401 if ( StatusEffectQueue_t::StatusEffectDefinitions_t::sustainedSpellDefinitionExists(effectID) )
6402 {
6403 auto& definition = StatusEffectQueue_t::StatusEffectDefinitions_t::getSustainedSpell(effectID);
6404 if ( effectID == SPELL_SHADOW_TAG )
6405 {
6406 int variation = 2;
6407 if ( players[player] && players[player]->entity )
6408 {
6409 if ( players[player]->entity->creatureShadowTaggedThisUid != 0
6410 && uidToEntity(players[player]->entity->creatureShadowTaggedThisUid) )
6411 {
6412 variation = 1;
6413 std::string formatString = definition.getName(variation).c_str();
6414 char buf[256] = "";
6415 Entity* tagged = uidToEntity(players[player]->entity->creatureShadowTaggedThisUid);
6416 if ( tagged->behavior == &actMonster )
6417 {
6418 int type = tagged->getMonsterTypeFromSprite();
6419 if ( type != NOTHING )
6420 {
6421 snprintf(buf, sizeof(buf), formatString.c_str(), getMonsterLocalizedName((Monster)type).c_str());
6422 }
6423 else
6424 {
6425 strcpy(buf, "");
6426 }
6427 }
6428 else if ( tagged->behavior == &actPlayer )
6429 {
6430 snprintf(buf, sizeof(buf), formatString.c_str(), stats[tagged->skill[2]]->name);
6431 }
6432 std::string formattedName = buf;
6433 uppercaseString(formattedName);
6434 tooltipHeader->setText(formattedName.c_str());
6435 }
6436 }
6437 if ( variation == 2 )
6438 {
6439 std::string newHeader = definition.getName(variation).c_str();
6440 uppercaseString(newHeader);
6441 tooltipHeader->setText(newHeader.c_str());
6442 }
6443 tooltipDesc->setText(definition.getDesc(-1).c_str()); // always -1 default desc
6444 tooltipInnerWidth = definition.tooltipWidth;
6445 }
6446 else
6447 {
6448 int variation = -1;
6449 std::string newHeader = definition.getName(variation).c_str();
6450 uppercaseString(newHeader);
6451 tooltipHeader->setText(newHeader.c_str());
6452 tooltipDesc->setText(definition.getDesc(variation).c_str());
6453 tooltipInnerWidth = definition.tooltipWidth;
6454 }
6455 }
6456 }
6457 else
6458 {
6459 int effectID = entry.effect;
6460 if ( StatusEffectQueue_t::StatusEffectDefinitions_t::effectDefinitionExists(effectID) )
6461 {
6462 auto& definition = StatusEffectQueue_t::StatusEffectDefinitions_t::getEffect(effectID);
6463 int variation = -1;
6464 if ( effectID == EFF_SHAPESHIFT )
6465 {
6466 if ( players[player] && players[player]->entity )
6467 {
6468 switch ( players[player]->entity->effectShapeshift )
6469 {
6470 case RAT:
6471 variation = 0;
6472 break;
6473 case SPIDER:
6474 variation = 1;
6475 break;
6476 case TROLL:
6477 variation = 2;
6478 break;
6479 case CREATURE_IMP:
6480 variation = 3;
6481 break;
6482 default:
6483 break;
6484 }
6485 }
6486 }
6487 else if ( effectID == EFF_VAMPIRICAURA )
6488 {
6489 bool sustained = false;
6490 for ( node_t* node = channeledSpells[player].first; node != nullptr; node = node->next )
6491 {
6492 spell_t* spell = (spell_t*)node->element;
6493 if ( spell && spell->ID == SPELL_VAMPIRIC_AURA )
6494 {
6495 sustained = true;
6496 break;
6497 }
6498 }
6499 if ( sustained )
6500 {
6501 variation = 1;
6502 }
6503 else
6504 {
6505 variation = 0;
6506 }
6507 }
6508 else if ( effectID == StatusEffectQueue_t::kEffectBread
6509 || effectID == StatusEffectQueue_t::kEffectBloodHunger )
6510 {
6511 if ( entry.customVariable >= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_OVERSATIATED) )
6512 {
6513 variation = 0;
6514 }
6515 else if ( entry.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_STARVING) )
6516 {
6517 variation = 3;
6518 }
6519 else if ( entry.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_WEAK) )
6520 {
6521 variation = 2;
6522 }
6523 else if ( entry.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_HUNGRY) )
6524 {
6525 variation = 1;
6526 }
6527 }
6528 else if ( effectID == StatusEffectQueue_t::kEffectAutomatonHunger )
6529 {
6530 int nameVariation = 1;
6531 int descVariation = 1;
6532 if ( entry.customVariable >= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_AUTOMATON_SUPERHEATED) )
6533 {
6534 nameVariation = 3;
6535 descVariation = 0;
6536 }
6537 else if ( entry.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_AUTOMATON_CRITICAL) )
6538 {
6539 nameVariation = 5;
6540 if ( svFlags & SV_FLAG_HUNGER )
6541 {
6542 descVariation = 2;
6543 }
6544 else
6545 {
6546 descVariation = 3;
6547 }
6548 }
6549 else
6550 {
6551 nameVariation = 4;
6552 descVariation = 1;
6553 }
6554
6555 std::string newHeader = definition.getName(nameVariation).c_str();
6556 uppercaseString(newHeader);
6557 tooltipHeader->setText(newHeader.c_str());
6558 tooltipDesc->setText(definition.getDesc(descVariation).c_str());
6559 tooltipInnerWidth = definition.tooltipWidth;
6560 }
6561 else if ( effectID == StatusEffectQueue_t::kEffectWanted
6562 || effectID == StatusEffectQueue_t::kEffectWantedInShop )
6563 {
6564 auto& definition2 = StatusEffectQueue_t::StatusEffectDefinitions_t::getEffect(kEffectWanted);
6565 std::string newHeader = definition2.getName(variation).c_str();
6566 uppercaseString(newHeader);
6567 tooltipHeader->setText(newHeader.c_str());
6568 tooltipDesc->setText("");
6569 tooltipInnerWidth = definition.tooltipWidth;
6570 if ( auto h = ShopkeeperPlayerHostility.getPlayerHostility(player) )
6571 {
6572 char buf[128];
6573 memset(buf, 0, sizeof(buf));
6574 snprintf(buf, sizeof(buf), definition.getDesc(0).c_str(), getMonsterLocalizedName(h->playerRace).c_str());
6575 std::string descStr = buf;
6576 if ( h->wantedLevel == ShopkeeperPlayerHostility.FAILURE_TO_IDENTIFY )
6577 {
6578 if ( descStr != "" ) { descStr += '\n'; }
6579 descStr += definition.getDesc(4);
6580 }
6581 if ( h->numKills > 0 )
6582 {
6583 //snprintf(buf, sizeof(buf), definition.getDesc(1).c_str(), h->numKills);
6584 if ( descStr != "" ) { descStr += '\n'; }
6585 //descStr += buf;
6586 descStr += definition.getDesc(1);
6587 }
6588 if ( h->numAggressions > 0 )
6589 {
6590 //snprintf(buf, sizeof(buf), definition.getDesc(2).c_str(), h->numAggressions);
6591 if ( descStr != "" ) { descStr += '\n'; }
6592 //descStr += buf;
6593 descStr += definition.getDesc(2);
6594 }
6595 if ( h->numAccessories > 0 )
6596 {
6597 //snprintf(buf, sizeof(buf), definition.getDesc(3).c_str(), h->numAccessories);
6598 if ( descStr != "" ) { descStr += '\n'; }
6599 //descStr += buf;
6600 descStr += definition.getDesc(3);
6601 }
6602 tooltipDesc->setText(descStr.c_str());
6603 }
6604 }
6605 else if ( effectID == StatusEffectQueue_t::kEffectDisabledHPRegen )
6606 {
6607 variation = 0;
6608 if ( !(svFlags & SV_FLAG_HUNGER) )
6609 {
6610 variation = 1;
6611 }
6612
6613 std::string newHeader = definition.getName(-1).c_str();
6614 uppercaseString(newHeader);
6615 tooltipHeader->setText(newHeader.c_str());
6616 tooltipDesc->setText(definition.getDesc(variation).c_str());
6617 tooltipInnerWidth = definition.tooltipWidth;
6618 }
6619 else if ( effectID == StatusEffectQueue_t::kEffectBountyTarget )
6620 {
6621 std::string newHeader = definition.getName(1).c_str();
6622 uppercaseString(newHeader);
6623 tooltipHeader->setText(newHeader.c_str());
6624 tooltipDesc->setText(definition.getDesc(variation).c_str());
6625 tooltipInnerWidth = definition.tooltipWidth;
6626 }
6627
6628 if ( effectID != StatusEffectQueue_t::kEffectAutomatonHunger
6629 && effectID != StatusEffectQueue_t::kEffectWanted
6630 && effectID != StatusEffectQueue_t::kEffectWantedInShop
6631 && effectID != StatusEffectQueue_t::kEffectBountyTarget
6632 && effectID != StatusEffectQueue_t::kEffectDisabledHPRegen )
6633 {
6634 std::string newHeader = definition.getName(variation).c_str();
6635 uppercaseString(newHeader);
6636 tooltipHeader->setText(newHeader.c_str());
6637 tooltipDesc->setText(definition.getDesc(variation).c_str());
6638 tooltipInnerWidth = definition.tooltipWidth;
6639 }
6640 }
6641 }
6642 }
6643 else
6644 {
6645 if ( entry.effect >= StatusEffectQueue_t::kSpellEffectOffset )
6646 {
6647 int effectID = entry.effect - StatusEffectQueue_t::kSpellEffectOffset;
6648 if ( StatusEffectQueue_t::StatusEffectDefinitions_t::sustainedSpellDefinitionExists(effectID) )
6649 {
6650 auto& definition = StatusEffectQueue_t::StatusEffectDefinitions_t::getSustainedSpell(effectID);
6651 tooltipInnerWidth = definition.tooltipWidth;
6652 }
6653 }
6654 else
6655 {
6656 int effectID = entry.effect;
6657 if ( StatusEffectQueue_t::StatusEffectDefinitions_t::effectDefinitionExists(effectID) )
6658 {
6659 auto& definition = StatusEffectQueue_t::StatusEffectDefinitions_t::getEffect(effectID);
6660 tooltipInnerWidth = definition.tooltipWidth;
6661 }
6662 }
6663 }
6664
6665 tooltipInnerWidth = std::max(tooltipInnerWidth, (int)tooltipHeader->getTextObject()->getWidth() + (tooltipHeader->getSize().x * 2));
6666
6667 const int padx = 16;
6668 const int pady1 = 4;
6669 tooltipHeader->setSize(SDL_Rect{ padx, pady1, tooltipInnerWidth, fontHeight + 4 });
6670
6671 auto descPos = tooltipDesc->getSize();
6672 descPos.w = tooltipInnerWidth;
6673 tooltipDesc->setSize(descPos);
6674 if ( refreshTooltip )
6675 {
6676 tooltipDesc->reflowTextToFit(0);
6677 }
6678
6679 const int pady2 = 6;
6680 descPos.x = padx + 4;
6681 descPos.y = tooltipHeader->getSize().y + tooltipHeader->getSize().h + pady2;
6682 descPos.h = tooltipDesc->getNumTextLines() * fontHeight + 4;
6683 tooltipDesc->setSize(descPos);
6684
6685 tooltipFrame->setDisabled(false);
6686 SDL_Rect tooltipPos;
6687 tooltipPos.w = tooltipInnerWidth + padx * 2;
6688 tooltipPos.h = tooltipHeader->getSize().y + tooltipHeader->getSize().h + pady2 + descPos.h + 8;
6689 tooltipPos.x = pos.x + pos.w;
6690 tooltipPos.y = pos.y - tooltipPos.h - 8;
6691 tooltipPos.y = std::max(0, tooltipPos.y);
6692 tooltipFrame->setSize(tooltipPos);
6693 imageResizeToContainer9x9(tooltipFrame, SDL_Rect{0, 0, tooltipPos.w, tooltipPos.h}, skillsheetEffectBackgroundImages);
6694 tooltipShowingEffectID = entry.effect;
6695 tooltipShowingEffectVariable = entry.customVariable;
6696 return true;
6697}
6698
6699const int StatusEffectQueue_t::kEffectBread = -2;
6700const int StatusEffectQueue_t::kEffectBloodHunger = -3;
6701const int StatusEffectQueue_t::kEffectAutomatonHunger = -4;
6702const int StatusEffectQueue_t::kEffectWanted = -5;
6703const int StatusEffectQueue_t::kEffectWantedInShop = -6;
6704const int StatusEffectQueue_t::kEffectBurning = -7;
6705const int StatusEffectQueue_t::kEffectFreeAction = -8;
6706const int StatusEffectQueue_t::kEffectLesserWarning = -9;
6707const int StatusEffectQueue_t::kEffectDisabledHPRegen = -10;
6708const int StatusEffectQueue_t::kEffectResistBurning = -11;
6709const int StatusEffectQueue_t::kEffectResistPoison = -12;
6710const int StatusEffectQueue_t::kEffectSlowDigestion = -13;
6711const int StatusEffectQueue_t::kEffectStrangulation = -14;
6712const int StatusEffectQueue_t::kEffectWarning = -15;
6713const int StatusEffectQueue_t::kEffectWaterBreathing = -16;
6714const int StatusEffectQueue_t::kEffectConflict = -17;
6715const int StatusEffectQueue_t::kEffectWaterWalking = -18;
6716const int StatusEffectQueue_t::kEffectLifesaving = -19;
6717const int StatusEffectQueue_t::kEffectPush = -20;
6718const int StatusEffectQueue_t::kEffectSneak = -21;
6719const int StatusEffectQueue_t::kEffectDrunkGoatman = -22;
6720const int StatusEffectQueue_t::kEffectBountyTarget = -23;
6721const int StatusEffectQueue_t::kEffectInspiration = -24;
6722const int StatusEffectQueue_t::kEffectRetaliation = -25;
6723const int StatusEffectQueue_t::kSpellEffectOffset = 10000;
6724
6725Frame* StatusEffectQueue_t::getStatusEffectFrame()
6726{
6727 return statusEffectFrame;
6728}
6729
6730void StatusEffectQueue_t::handleNavigation(std::map<int, StatusEffectQueueEntry_t*>& grid,
6731 bool& tooltipShowing, const bool hungerEffectInEffectQueue)
6732{
6733 if ( !inputs.hasController(player)
6734 || inputs.getVirtualMouse(player)->draw_cursor
6735 || players[player]->shootmode )
6736 {
6737 return;
6738 }
6739
6740 if ( players[player]->GUI.activeModule != Player::GUI_t::MODULE_STATUS_EFFECTS
6741 && players[player]->GUI.activeModule == Player::GUI_t::MODULE_INVENTORY )
6742 {
6743
6744 }
6745
6746 if ( players[player]->GUI.activeModule != Player::GUI_t::MODULE_STATUS_EFFECTS )
6747 {
6748 return;
6749 }
6750
6751 if ( effectQueue.size() == 0 )
6752 {
6753 return;
6754 }
6755
6756
6757 StatusEffectQueueEntry_t::Dir_t inputDirection = StatusEffectQueueEntry_t::Dir_t::NONE;
6758 if ( Input::inputs[player].consumeBinaryToggle("InventoryMoveUp") )
6759 {
6760 inputDirection = StatusEffectQueueEntry_t::Dir_t::UP;
6761 }
6762 if ( Input::inputs[player].consumeBinaryToggle("InventoryMoveUpAnalog") )
6763 {
6764 inputDirection = StatusEffectQueueEntry_t::Dir_t::UP;
6765 }
6766 if ( Input::inputs[player].consumeBinaryToggle("InventoryMoveDown") )
6767 {
6768 inputDirection = StatusEffectQueueEntry_t::Dir_t::DOWN;
6769 }
6770 if ( Input::inputs[player].consumeBinaryToggle("InventoryMoveDownAnalog") )
6771 {
6772 inputDirection = StatusEffectQueueEntry_t::Dir_t::DOWN;
6773 }
6774 if ( Input::inputs[player].consumeBinaryToggle("InventoryMoveLeft") )
6775 {
6776 inputDirection = StatusEffectQueueEntry_t::Dir_t::LEFT;
6777 }
6778 if ( Input::inputs[player].consumeBinaryToggle("InventoryMoveLeftAnalog") )
6779 {
6780 inputDirection = StatusEffectQueueEntry_t::Dir_t::LEFT;
6781 }
6782 if ( Input::inputs[player].consumeBinaryToggle("InventoryMoveRight") )
6783 {
6784 inputDirection = StatusEffectQueueEntry_t::Dir_t::RIGHT;
6785 }
6786 if ( Input::inputs[player].consumeBinaryToggle("InventoryMoveRightAnalog") )
6787 {
6788 inputDirection = StatusEffectQueueEntry_t::Dir_t::RIGHT;
6789 }
6790
6791 selectedElement = std::max(0, selectedElement);
6792 selectedElement = std::min(selectedElement, (int)(effectQueue.size() - 1));
6793
6794 StatusEffectQueueEntry_t* selectedEntry = nullptr;
6795
6796 int miny = 10;
6797 int maxy = 0;
6798 std::vector<int>minx;
6799 std::vector<int>maxx;
6800 for ( int i = 0; i < 32; ++i )
6801 {
6802 maxx.push_back(0);
6803 minx.push_back(32);
6804 }
6805 for ( auto& slot : grid )
6806 {
6807 int x = slot.first % 10000;
6808 int y = slot.first / 10000;
6809
6810 miny = std::min(y, miny);
6811 maxy = std::max(y, maxy);
6812 minx[y] = std::min(x, minx[y]);
6813 maxx[y] = std::max(x, maxx[y]);
6814 }
6815
6816 // clear navigation graph
6817 for ( auto& q : effectQueue )
6818 {
6819 q.navigation.clear();
6820 }
6821
6822 for ( auto& slot : grid )
6823 {
6824 int x = slot.first % 10000;
6825 int y = slot.first / 10000;
6826
6827 if ( slot.second->index == (size_t)selectedElement )
6828 {
6829 selectedEntry = slot.second;
6830 }
6831
6832 if ( grid.find((x + 1) + y * 10000) != grid.end() )
6833 {
6834 auto dest = grid[(x + 1) + y * 10000];
6835 slot.second->navigation[StatusEffectQueueEntry_t::Dir_t::RIGHT] = dest->index;
6836 }
6837 else
6838 {
6839 if ( hungerEffectInEffectQueue )
6840 {
6841 auto dest = grid[0 + 0 * 10000];
6842 slot.second->navigation[StatusEffectQueueEntry_t::Dir_t::RIGHT] = dest->index;
6843 }
6844 else
6845 {
6846 auto dest = grid[(minx[y]) + y * 10000];
6847 slot.second->navigation[StatusEffectQueueEntry_t::Dir_t::RIGHT] = dest->index;
6848 }
6849 }
6850 if ( grid.find((x - 1) + y * 10000) != grid.end() )
6851 {
6852 auto dest = grid[(x - 1) + y * 10000];
6853 slot.second->navigation[StatusEffectQueueEntry_t::Dir_t::LEFT] = dest->index;
6854 }
6855 else
6856 {
6857 if ( hungerEffectInEffectQueue && !(x == 0 && y == 0) )
6858 {
6859 auto dest = grid[0 + 0 * 10000];
6860 slot.second->navigation[StatusEffectQueueEntry_t::Dir_t::LEFT] = dest->index;
6861 }
6862 else
6863 {
6864 auto dest = grid[maxx[y] + y * 10000];
6865 slot.second->navigation[StatusEffectQueueEntry_t::Dir_t::LEFT] = dest->index;
6866 }
6867 }
6868 if ( grid.find(x + (y + 1) * 10000) != grid.end() )
6869 {
6870 auto dest = grid[x + (y + 1) * 10000];
6871 slot.second->navigation[StatusEffectQueueEntry_t::Dir_t::UP] = dest->index;
6872 }
6873 else
6874 {
6875 int destx = std::min(std::max(x, minx[0]), maxx[0]);
6876 auto dest = grid[destx + (0) * 10000];
6877 slot.second->navigation[StatusEffectQueueEntry_t::Dir_t::UP] = dest->index;
6878 }
6879 if ( grid.find(x + (y - 1) * 10000) != grid.end() )
6880 {
6881 auto dest = grid[x + (y - 1) * 10000];
6882 slot.second->navigation[StatusEffectQueueEntry_t::Dir_t::DOWN] = dest->index;
6883 }
6884 else
6885 {
6886 int desty = maxy;
6887 while ( desty > 0 )
6888 {
6889 if ( grid.find(x + (desty) * 10000) != grid.end() )
6890 {
6891 auto dest = grid[x + (desty) * 10000];
6892 slot.second->navigation[StatusEffectQueueEntry_t::Dir_t::DOWN] = dest->index;
6893 break;
6894 }
6895 --desty;
6896 }
6897 }
6898 }
6899
6900 if ( selectedEntry && inputDirection != StatusEffectQueueEntry_t::Dir_t::NONE )
6901 {
6902 auto findIndex = selectedEntry->navigation.find(inputDirection);
6903 if ( findIndex != selectedEntry->navigation.end() )
6904 {
6905 selectedElement = (*findIndex).second;
6906 Player::soundMovement();
6907 }
6908 }
6909
6910 auto innerFrame = statusEffectFrame->findFrame("effects");
6911 auto& frameImages = innerFrame->getImages();
6912 auto frameImagesIterator = frameImages.begin();
6913 for ( auto it = effectQueue.rbegin(); it != effectQueue.rend(); )
6914 {
6915 auto& q = (*it);
6916
6917 Frame::image_t* frameImg = nullptr;
6918 if ( frameImagesIterator != frameImages.end() )
6919 {
6920 frameImg = *frameImagesIterator;
6921 }
6922
6923 if ( q.index == selectedElement )
6924 {
6925 SDL_Rect size = statusEffectFrame->getAbsoluteSize();
6926 int mouseDetectionPadding = 2;
6927
6928 SDL_Rect frameImgPos = frameImg->pos;
6929 bool oldDisabled = frameImg->disabled;
6930 updateEntryImage(q, frameImg);
6931 frameImg->disabled = oldDisabled;
6932 frameImgPos.x = q.animateSetpointX;
6933 frameImgPos.y = q.animateSetpointY;
6934
6935 size.x += frameImgPos.x - (mouseDetectionPadding);
6936 size.y += frameImgPos.y - (mouseDetectionPadding);
6937 size.w = frameImgPos.w + (mouseDetectionPadding * 2);
6938 size.h = frameImgPos.h + (mouseDetectionPadding * 2);
6939 tooltipShowing = doStatusEffectTooltip(q, size);
6940 players[player]->hud.setCursorDisabled(false);
6941
6942 // make sure to adjust absolute size to camera viewport
6943 size.x -= players[player]->camera_virtualx1();
6944 size.y -= players[player]->camera_virtualy1();
6945
6946 players[player]->hud.updateCursorAnimation(size.x - 1 + mouseDetectionPadding, size.y - 1 + mouseDetectionPadding,
6947 frameImgPos.w, frameImgPos.h, inputs.getVirtualMouse(player)->draw_cursor);
6948 break;
6949 }
6950
6951 ++it;
6952 if ( frameImagesIterator != frameImages.end() )
6953 {
6954 ++frameImagesIterator;
6955 }
6956 }
6957}
6958
6959const bool kAllowGhostStatusEffects = false;
6960
6961void StatusEffectQueue_t::updateAllQueuedEffects()
6962{
6963 bool effectsEnabled = true;
6964 if ( !players[player]->entity && ((multiplayer == SINGLE0 && splitscreen) || multiplayer != SINGLE0) )
6965 {
6966 effectsEnabled = false;
6967 resetQueue();
6968 }
6969 if ( players[player]->ghost.isActive() && !kAllowGhostStatusEffects )
6970 {
6971 effectsEnabled = false;
6972 resetQueue();
6973 }
6974
6975 std::unordered_set<int> effectSet;
6976 for ( auto it = effectQueue.rbegin(); it != effectQueue.rend(); ++it )
6977 {
6978 effectSet.insert((*it).effect);
6979 }
6980
6981 std::vector<int> effectsToSkipAnimThisFrame;
6982 std::set<int> effectsToSkipAnim;
6983 std::unordered_set<int> spellsActive;
6984 int count = 0; //This is just for debugging purposes.
6985 for ( node_t* node = channeledSpells[player].first; node && effectsEnabled; node = node->next, count++ )
6986 {
6987 spell_t* spell = (spell_t*)node->element;
6988 if ( !spell )
6989 {
6990 break;
6991 }
6992 spellsActive.insert(spell->ID);
6993 if ( StatusEffectDefinitions_t::sustainedSpellDefinitionExists(spell->ID) )
6994 {
6995 if ( StatusEffectDefinitions_t::getSustainedSpell(spell->ID).effect_id == -1 )
6996 {
6997 // unique sustained effect
6998 int effectID = spell->ID + kSpellEffectOffset;
6999 if ( effectSet.find(effectID) == effectSet.end() )
7000 {
7001 insertEffect(-1, spell->ID);
7002 }
7003 }
7004 }
7005 }
7006
7007 for ( auto& eff : effectSet )
7008 {
7009 if ( eff >= kSpellEffectOffset && (eff - kSpellEffectOffset) == SPELL_SHADOW_TAG )
7010 {
7011 if ( players[player] && players[player]->entity
7012 && players[player]->entity->creatureShadowTaggedThisUid != 0
7013 && uidToEntity(players[player]->entity->creatureShadowTaggedThisUid) )
7014 {
7015 // shadow tag still active, check uid
7016 for ( auto it = effectQueue.rbegin(); it != effectQueue.rend(); ++it )
7017 {
7018 if ( (*it).effect == kSpellEffectOffset + SPELL_SHADOW_TAG )
7019 {
7020 if ( (*it).customVariable != players[player]->entity->creatureShadowTaggedThisUid )
7021 {
7022 deleteEffect(eff);
7023 break;
7024 }
7025 }
7026 }
7027 }
7028 else
7029 {
7030 deleteEffect(eff);
7031 }
7032 }
7033 else if ( eff >= kSpellEffectOffset && spellsActive.find(eff - kSpellEffectOffset) == spellsActive.end() )
7034 {
7035 // effect has expired.
7036 deleteEffect(eff);
7037 }
7038 }
7039
7040 for ( int i = 0; i <= NUMEFFECTS && effectsEnabled; ++i )
7041 {
7042 if ( i == NUMEFFECTS )
7043 {
7044 if ( players[player] && players[player]->entity )
7045 {
7046 if ( players[player]->entity->creatureShadowTaggedThisUid != 0
7047 && uidToEntity(players[player]->entity->creatureShadowTaggedThisUid) )
7048 {
7049 if ( insertEffect(-1, SPELL_SHADOW_TAG) )
7050 {
7051 effectQueue.back().customVariable = players[player]->entity->creatureShadowTaggedThisUid;
7052 notificationQueue.back().customVariable = players[player]->entity->creatureShadowTaggedThisUid;
7053 }
7054 }
7055 }
7056 }
7057 else
7058 {
7059 bool skipAnim = false;
7060 bool effectActive = stats[player]->EFFECTS[i];
7061 if ( i == EFF_LEVITATING && !effectActive )
7062 {
7063 bool tmp = stats[player]->EFFECTS[EFF_FLUTTER];
7064 stats[player]->EFFECTS[EFF_FLUTTER] = false;
7065 effectActive = isLevitating(stats[player]);
7066 stats[player]->EFFECTS[EFF_FLUTTER] = tmp;
7067 }
7068 else if ( i == EFF_MAGICREFLECT && !effectActive )
7069 {
7070 if ( stats[player]->amulet )
7071 {
7072 if ( stats[player]->amulet->type == AMULET_MAGICREFLECTION )
7073 {
7074 effectActive = true;
7075 }
7076 }
7077 if ( stats[player]->cloak )
7078 {
7079 if ( stats[player]->cloak->type == CLOAK_MAGICREFLECTION )
7080 {
7081 effectActive = true;
7082 }
7083 }
7084 }
7085 else if ( i == EFF_INVISIBLE && !effectActive && players[player]->entity )
7086 {
7087 bool oldSneaking = stats[player]->sneaking;
7088 stats[player]->sneaking = false;
7089 bool activeWithoutSneak = players[player]->entity->isInvisible();
7090 stats[player]->sneaking = oldSneaking;
7091 effectActive = players[player]->entity->isInvisible();
7092 if ( !activeWithoutSneak )
7093 {
7094 skipAnim = true;
7095 effectsToSkipAnim.insert(i);
7096 }
7097 }
7098 else if ( i == EFF_TELEPATH )
7099 {
7100 skipAnim = true;
7101 effectsToSkipAnim.insert(i);
7102 }
7103 else if ( i == EFF_BLIND )
7104 {
7105 if ( stats[player]->mask && stats[player]->mask->type == TOOL_BLINDFOLD_TELEPATHY )
7106 {
7107 skipAnim = true;
7108 effectsToSkipAnim.insert(i);
7109 }
7110 }
7111 else if ( i == EFF_DRUNK && stats[player]->type == GOATMAN )
7112 {
7113 effectActive = false;
7114 }
7115
7116 if ( !players[player]->entity )
7117 {
7118 skipAnim = true;
7119 }
7120
7121 if ( effectActive )
7122 {
7123 if ( effectSet.find(i) == effectSet.end() )
7124 {
7125 if ( i == EFF_SHAPESHIFT )
7126 {
7127 if ( players[player] && players[player]->entity )
7128 {
7129 insertEffect(i, -1);
7130 }
7131 }
7132 else
7133 {
7134 insertEffect(i, -1);
7135 }
7136 if ( skipAnim )
7137 {
7138 effectsToSkipAnimThisFrame.push_back(i);
7139 }
7140 }
7141 else if ( i == EFF_SHAPESHIFT )
7142 {
7143 if ( !players[player] || !players[player]->entity )
7144 {
7145 deleteEffect(i);
7146 }
7147 }
7148 }
7149 else
7150 {
7151 if ( effectSet.find(i) != effectSet.end() )
7152 {
7153 deleteEffect(i);
7154 }
7155 }
7156 }
7157 }
7158
7159 auto wantedLevel = ShopkeeperPlayerHostility.getWantedLevel(player);
7160 bool wantedOutsideOfShop = false;
7161 bool wantedInsideShop = false;
7162 bool inshop = false;
7163
7164 std::map<int, bool> miscEffects;
7165 for ( int i = kEffectBurning; i >= kEffectRetaliation; --i )
7166 {
7167 miscEffects[i] = false;
7168 }
7169 if ( players[player] && players[player]->ghost.isActive() && kAllowGhostStatusEffects )
7170 {
7171 if ( players[player]->ghost.my->skill[3] == 1 ) // ghost sneaking
7172 {
7173 miscEffects[kEffectSneak] = true;
7174 }
7175 }
7176 if ( players[player] && players[player]->entity )
7177 {
7178 if ( players[player]->entity->flags[BURNING9] )
7179 {
7180 miscEffects[kEffectBurning] = true;
7181 }
7182 if ( !(svFlags & SV_FLAG_HUNGER) )
7183 {
7184 miscEffects[kEffectDisabledHPRegen] = true;
7185 }
7186 if ( stats[player] )
7187 {
7188 bool cursedItemIsBuff = shouldInvertEquipmentBeatitude(stats[player]);
7189 if ( ((stats[player]->mask && stats[player]->mask->type == TOOL_BLINDFOLD_FOCUS)
7190 || (stats[player]->type == GOATMAN && stats[player]->EFFECTS[EFF_DRUNK])) )
7191 {
7192 miscEffects[kEffectFreeAction] = true;
7193 }
7194 if ( stats[player]->type == GOATMAN && stats[player]->EFFECTS[EFF_DRUNK] )
7195 {
7196 miscEffects[kEffectDrunkGoatman] = true;
7197 }
7198 if ( stats[player]->helmet && stats[player]->helmet->type == HAT_BOUNTYHUNTER )
7199 {
7200 for ( auto target : achievementObserver.playerAchievements[player].bountyTargets )
7201 {
7202 if ( uidToEntity(target) )
7203 {
7204 miscEffects[kEffectBountyTarget] = true;
7205 break;
7206 }
7207 }
7208 }
7209 if ( stats[player]->mask && stats[player]->mask->type == MASK_MOUTHKNIFE )
7210 {
7211 miscEffects[kEffectRetaliation] = true;
7212 }
7213 if ( stats[player]->helmet &&
7214 (stats[player]->helmet->type == HAT_LAURELS
7215 || stats[player]->helmet->type == HAT_TURBAN
7216 || stats[player]->helmet->type == HAT_CROWN) )
7217 {
7218 miscEffects[kEffectInspiration] = true;
7219 }
7220 if ( stats[player]->shoes && stats[player]->shoes->type == ARTIFACT_BOOTS )
7221 {
7222 miscEffects[kEffectLesserWarning] = true;
7223 }
7224 if ( stats[player]->breastplate && stats[player]->breastplate->type == VAMPIRE_DOUBLET )
7225 {
7226 if ( (svFlags & SV_FLAG_HUNGER) )
7227 {
7228 miscEffects[kEffectDisabledHPRegen] = true;
7229 }
7230 }
7231 if ( stats[player]->breastplate && stats[player]->breastplate->type == MACHINIST_APRON )
7232 {
7233 miscEffects[kEffectResistBurning] = true;
7234 }
7235 if ( stats[player]->amulet && stats[player]->amulet->type == AMULET_POISONRESISTANCE )
7236 {
7237 miscEffects[kEffectResistPoison] = true;
7238 }
7239 if ( stats[player]->ring && stats[player]->ring->type == RING_SLOWDIGESTION
7240 && (stats[player]->ring->beatitude >= 0 || cursedItemIsBuff) )
7241 {
7242 if ( (svFlags & SV_FLAG_HUNGER) )
7243 {
7244 miscEffects[kEffectSlowDigestion] = true;
7245 }
7246 }
7247 if ( stats[player]->amulet && stats[player]->amulet->type == AMULET_STRANGULATION
7248 && stats[player]->type != SKELETON )
7249 {
7250 miscEffects[kEffectStrangulation] = true;
7251 }
7252 if ( stats[player]->amulet && stats[player]->amulet->type == AMULET_WATERBREATHING )
7253 {
7254 miscEffects[kEffectWaterBreathing] = true;
7255 }
7256 if ( stats[player]->ring && stats[player]->ring->type == RING_WARNING )
7257 {
7258 miscEffects[kEffectWarning] = true;
7259 }
7260 if ( stats[player]->ring && stats[player]->ring->type == RING_CONFLICT )
7261 {
7262 miscEffects[kEffectConflict] = true;
7263 }
7264 if ( (stats[player]->ring && stats[player]->ring->type == RING_STRENGTH)
7265 || (stats[player]->gloves && stats[player]->gloves->type == GAUNTLETS_STRENGTH)
7266 || stats[player]->EFFECTS[EFF_POTION_STR] )
7267 {
7268 miscEffects[kEffectPush] = true;
7269 }
7270 if ( (stats[player]->shoes && stats[player]->shoes->type == IRON_BOOTS_WATERWALKING)
7271 || skillCapstoneUnlocked(player, PRO_SWIMMING) )
7272 {
7273 miscEffects[kEffectWaterWalking] = true;
7274 }
7275 if ( (stats[player]->amulet && stats[player]->amulet->type == AMULET_LIFESAVING)
7276 || (((stats[player]->playerRace == RACE_SKELETON && stats[player]->appearance == 0)
7277 || stats[player]->type == SKELETON) && stats[player]->MP >= 75) )
7278 {
7279 miscEffects[kEffectLifesaving] = true;
7280 }
7281 if ( stats[player]->sneaking == 1 && !stats[player]->defending && !skillCapstoneUnlocked(player, PRO_STEALTH) )
7282 {
7283 miscEffects[kEffectSneak] = true;
7284 }
7285 }
7286
7287 int playerx = static_cast<int>(players[player]->entity->x) >> 4;
7288 int playery = static_cast<int>(players[player]->entity->y) >> 4;
7289 if ( playerx >= 0 && playerx < map.width && playery >= 0 && playery < map.height )
7290 {
7291 // if the criminal was inside a shop
7292 if ( shoparea[playery + playerx * map.height] )
7293 {
7294 inshop = true;
7295 }
7296 }
7297 if ( wantedLevel != ShopkeeperPlayerHostility_t::NO_WANTED_LEVEL )
7298 {
7299 /*if ( inshop ) // if we want a re-notify while in the shop
7300 {
7301 if ( effectSet.find(kEffectWantedInShop) == effectSet.end() )
7302 {
7303 insertEffect(kEffectWantedInShop, -1);
7304 }
7305 if ( effectSet.find(kEffectWanted) != effectSet.end() )
7306 {
7307 deleteEffect(kEffectWanted);
7308 }
7309 }
7310 else*/
7311 {
7312 if ( effectSet.find(kEffectWantedInShop) != effectSet.end() )
7313 {
7314 if ( effectSet.find(kEffectWanted) != effectSet.end() )
7315 {
7316 deleteEffect(kEffectWanted);
7317 }
7318 for ( auto it = effectQueue.begin(); it != effectQueue.end(); )
7319 {
7320 if ( (*it).effect == kEffectWantedInShop )
7321 {
7322 (*it).effect = kEffectWanted;
7323 break;
7324 }
7325 ++it;
7326 }
7327 }
7328 else if ( effectSet.find(kEffectWanted) == effectSet.end() )
7329 {
7330 insertEffect(kEffectWanted, -1);
7331 }
7332 }
7333 }
7334 }
7335
7336 for ( int i = kEffectBurning; i >= kEffectRetaliation; --i )
7337 {
7338 if ( miscEffects[i] == false )
7339 {
7340 if ( effectSet.find(i) != effectSet.end() )
7341 {
7342 deleteEffect(i);
7343 }
7344 }
7345 else
7346 {
7347 if ( !players[player]->entity )
7348 {
7349 effectsToSkipAnim.insert(i);
7350 }
7351 if ( i == kEffectSneak )
7352 {
7353 effectsToSkipAnim.insert(i);
7354 }
7355 if ( effectSet.find(i) == effectSet.end() )
7356 {
7357 insertEffect(i, -1);
7358
7359 if ( i == kEffectSneak )
7360 {
7361 effectsToSkipAnimThisFrame.push_back(i);
7362 }
7363 }
7364 }
7365 }
7366
7367 if ( wantedLevel == ShopkeeperPlayerHostility_t::NO_WANTED_LEVEL )
7368 {
7369 if ( effectSet.find(kEffectWanted) != effectSet.end() )
7370 {
7371 deleteEffect(kEffectWanted);
7372 }
7373 if ( effectSet.find(kEffectWantedInShop) != effectSet.end() )
7374 {
7375 deleteEffect(kEffectWantedInShop);
7376 }
7377 }
7378
7379 bool hungerIconActive = (effectSet.find(kEffectBread) != effectSet.end()
7380 || effectSet.find(kEffectBloodHunger) != effectSet.end()
7381 || effectSet.find(kEffectAutomatonHunger) != effectSet.end());
7382 if ( !players[player]->entity )
7383 {
7384 if ( effectSet.find(kEffectBread) != effectSet.end() )
7385 {
7386 effectsToSkipAnimThisFrame.push_back(kEffectBread);
7387 }
7388 if ( effectSet.find(kEffectBloodHunger) != effectSet.end() )
7389 {
7390 effectsToSkipAnimThisFrame.push_back(kEffectBloodHunger);
7391 }
7392 if ( effectSet.find(kEffectAutomatonHunger) != effectSet.end() )
7393 {
7394 effectsToSkipAnimThisFrame.push_back(kEffectAutomatonHunger);
7395 }
7396 }
7397
7398 Frame* statusEffectFrame = getStatusEffectFrame();
7399 auto automatonHungerFrame = statusEffectFrame->findFrame("automaton hunger notification");
7400 automatonHungerFrame->setDisabled(true);
7401 auto automatonFlameImg = automatonHungerFrame->findImage("flame");
7402
7403 int iconSize = 32;
7404 int movex = splitscreen ? 4 : 0;
7405 if ( hungerIconActive )
7406 {
7407 if ( effectSet.find(kEffectBread) != effectSet.end() )
7408 {
7409 movex += 76;
7410 }
7411 else
7412 {
7413 movex += 64;
7414 }
7415 movex += 4;
7416 }
7417 const int startrowx = movex;
7418 int movey = statusEffectFrame->getSize().h - iconSize;
7419 const int spacing = 36;
7420 int numEffectsOnLine = 0;
7421
7422 for ( auto eff : effectsToSkipAnimThisFrame )
7423 {
7424 for ( auto& notif : notificationQueue )
7425 {
7426 if ( notif.effect == eff )
7427 {
7428 notif.notificationState = StatusEffectQueueEntry_t::STATE_END;
7429 notif.notificationStateInit = StatusEffectQueueEntry_t::STATE_END;
7430
7431 notif.notificationTargetPosition.x = movex;
7432
7433 for ( auto& entry : effectQueue )
7434 {
7435 if ( entry.effect == eff )
7436 {
7437 entry.animateSetpointX = notif.notificationTargetPosition.x;
7438 entry.animateSetpointY = notif.notificationTargetPosition.y;
7439 entry.animateSetpointW = entry.notificationTargetPosition.w;
7440 entry.animateSetpointH = entry.notificationTargetPosition.h;
7441
7442 entry.animateStartX = entry.animateSetpointX;
7443 entry.animateStartY = entry.animateSetpointY;
7444 entry.animateStartW = entry.animateSetpointW;
7445 entry.animateStartH = entry.animateSetpointH;
7446 entry.pos.x = entry.animateSetpointX;
7447 entry.pos.y = entry.animateSetpointY;
7448 entry.pos.w = entry.animateSetpointW;
7449 entry.pos.h = entry.animateSetpointH;
7450
7451 notif.pos = entry.pos;
7452 }
7453 }
7454 }
7455 }
7456 }
7457
7458 auto notificationFrame = statusEffectFrame->findFrame("notification frame");
7459 notificationFrame->setDisabled(true);
7460 auto notificationImg = notificationFrame->findImage("notification img");
7461 notificationImg->disabled = true;
7462 auto notificationTxt = notificationFrame->findField("notification txt");
7463 notificationTxt->setDisabled(true);
7464 notificationTxt->setFont(StatusEffectDefinitions_t::notificationFont.c_str());
7465 notificationTxt->setColor(StatusEffectDefinitions_t::notificationTextColor);
7466 if ( notificationQueue.size() >= 1 )
7467 {
7468 auto& notif = notificationQueue.front();
7469 notif.animateNotification(player);
7470 updateEntryImage(notif, notificationImg);
7471 if ( notif.notificationState == StatusEffectQueueEntry_t::STATE_2
7472 || notif.notificationState == StatusEffectQueueEntry_t::STATE_3 )
7473 {
7474 if ( notif.effect >= kSpellEffectOffset )
7475 {
7476 int effectID = notif.effect - kSpellEffectOffset;
7477 if ( StatusEffectDefinitions_t::sustainedSpellDefinitionExists(effectID) )
7478 {
7479 auto& definition = StatusEffectDefinitions_t::getSustainedSpell(effectID);
7480 if ( notif.notificationState == StatusEffectQueueEntry_t::STATE_2 )
7481 {
7482 int variation = -1;
7483 if ( effectID == SPELL_SHADOW_TAG )
7484 {
7485 variation = 0;
7486 notificationTxt->setText("");
7487 if ( players[player] && players[player]->entity )
7488 {
7489 if ( players[player]->entity->creatureShadowTaggedThisUid != 0
7490 && uidToEntity(players[player]->entity->creatureShadowTaggedThisUid) )
7491 {
7492 std::string formatString = definition.getName(variation).c_str();
7493 char buf[256] = "";
7494 Entity* tagged = uidToEntity(players[player]->entity->creatureShadowTaggedThisUid);
7495 if ( tagged->behavior == &actMonster )
7496 {
7497 int type = tagged->getMonsterTypeFromSprite();
7498 if ( type != NOTHING )
7499 {
7500 snprintf(buf, sizeof(buf), formatString.c_str(), getMonsterLocalizedName((Monster)type).c_str());
7501 }
7502 else
7503 {
7504 strcpy(buf, "");
7505 }
7506 }
7507 else if ( tagged->behavior == &actPlayer )
7508 {
7509 snprintf(buf, sizeof(buf), formatString.c_str(), stats[tagged->skill[2]]->name);
7510 }
7511 std::string formattedName = buf;
7512 notificationTxt->setText(formattedName.c_str());
7513 }
7514 }
7515 }
7516 else
7517 {
7518 notificationTxt->setText(definition.getName(variation).c_str());
7519 }
7520 }
7521 notificationTxt->setDisabled(false);
7522 }
7523 }
7524 else
7525 {
7526 int effectID = notif.effect;
7527 if ( StatusEffectDefinitions_t::effectDefinitionExists(effectID) )
7528 {
7529 auto& definition = StatusEffectDefinitions_t::getEffect(effectID);
7530 if ( notif.notificationState == StatusEffectQueueEntry_t::STATE_2 )
7531 {
7532 int variation = -1;
7533 if ( effectID == EFF_SHAPESHIFT )
7534 {
7535 if ( players[player] && players[player]->entity )
7536 {
7537 switch ( players[player]->entity->effectShapeshift )
7538 {
7539 case RAT:
7540 variation = 0;
7541 break;
7542 case SPIDER:
7543 variation = 1;
7544 break;
7545 case TROLL:
7546 variation = 2;
7547 break;
7548 case CREATURE_IMP:
7549 variation = 3;
7550 break;
7551 default:
7552 break;
7553 }
7554 }
7555 }
7556 else if ( effectID == StatusEffectQueue_t::kEffectBread
7557 || effectID == StatusEffectQueue_t::kEffectBloodHunger )
7558 {
7559 if ( notif.customVariable >= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_OVERSATIATED) )
7560 {
7561 variation = 0;
7562 }
7563 else if ( notif.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_STARVING) )
7564 {
7565 variation = 3;
7566 }
7567 else if ( notif.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_WEAK) )
7568 {
7569 variation = 2;
7570 }
7571 else if ( notif.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_HUNGRY) )
7572 {
7573 variation = 1;
7574 }
7575 }
7576 else if ( effectID == StatusEffectQueue_t::kEffectAutomatonHunger )
7577 {
7578 if ( notif.customVariable >= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_AUTOMATON_SUPERHEATED) )
7579 {
7580 variation = 0;
7581 }
7582 else if ( notif.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_AUTOMATON_CRITICAL) )
7583 {
7584 variation = 2;
7585 }
7586 else
7587 {
7588 variation = 1;
7589 }
7590 }
7591 else if ( effectID == StatusEffectQueue_t::kEffectBountyTarget )
7592 {
7593 variation = 0;
7594 }
7595 else if ( effectID == EFF_VAMPIRICAURA )
7596 {
7597 bool sustained = false;
7598 for ( node_t* node = channeledSpells[player].first; node != nullptr; node = node->next )
7599 {
7600 spell_t* spell = (spell_t*)node->element;
7601 if ( spell && spell->ID == SPELL_VAMPIRIC_AURA )
7602 {
7603 sustained = true;
7604 break;
7605 }
7606 }
7607 if ( sustained )
7608 {
7609 variation = 1;
7610 }
7611 else
7612 {
7613 variation = 0;
7614 }
7615 }
7616 notificationTxt->setText(definition.getName(variation).c_str());
7617 }
7618 if ( notificationImg->path != "" )
7619 {
7620 notificationTxt->setDisabled(false);
7621 }
7622 }
7623 }
7624 if ( auto textGet = notificationTxt->getTextObject() )
7625 {
7626 SDL_Rect txtPos;
7627 txtPos.w = textGet->getWidth();
7628 txtPos.h = textGet->getHeight() * textGet->getNumTextLines() + 4;
7629
7630 const int movementAmount = getStatusEffectMovementAmount(player);
7631 txtPos.x = (getBaseEffectPosX() - movementAmount - (notif.getStatusEffectLargestScaling(player) - 1.0) * notif.getEffectSpriteNormalWidth() / 2)
7632 + (notif.getEffectSpriteNormalWidth() * notif.getStatusEffectLargestScaling(player)) / 2 - txtPos.w / 2;
7633 txtPos.y = getBaseEffectPosY() - movementAmount - txtPos.h;
7634 notificationTxt->setSize(txtPos);
7635 }
7636 }
7637 if ( notif.notificationState == StatusEffectQueueEntry_t::STATE_END )
7638 {
7639 notificationQueue.pop_front();
7640 }
7641
7642 if ( notif.effect == kEffectAutomatonHunger )
7643 {
7644 automatonHungerFrame->setDisabled(false);
7645 automatonHungerFrame->setSize(notificationImg->pos);
7646 }
7647 }
7648
7649 if ( !notificationImg->disabled || !notificationTxt->isDisabled() )
7650 {
7651 notificationFrame->setDisabled(false);
7652 notificationFrame->setSize(SDL_Rect{ 0, 0, statusEffectFrame->getSize().w, statusEffectFrame->getSize().h });
7653 }
7654
7655
7656 auto innerFrame = statusEffectFrame->findFrame("effects");
7657 int numFrameImages = innerFrame->getImages().size();
7658 while ( effectQueue.size() > numFrameImages )
7659 {
7660 auto img = innerFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "inner img");
7661 img->disabled = true;
7662 numFrameImages = innerFrame->getImages().size();
7663 }
7664 while ( effectQueue.size() < numFrameImages )
7665 {
7666 innerFrame->getImages().erase(innerFrame->getImages().begin());
7667 numFrameImages = innerFrame->getImages().size();
7668 }
7669 auto& frameImages = innerFrame->getImages();
7670 for ( auto img : frameImages )
7671 {
7672 img->disabled = true;
7673 }
7674
7675 auto frameImagesIterator = frameImages.begin();
7676 bool bFrameCapturesMouse = false;
7677 if ( !players[player]->shootmode && inputs.getVirtualMouse(player)->draw_cursor
7678 && !players[player]->skillSheet.bSkillSheetOpen
7679 && !players[player]->bookGUI.bBookOpen
7680 && !players[player]->signGUI.bSignOpen
7681 && !(players[player]->bUseCompactGUIHeight() && !players[player]->hud.statusFxFocusedWindowActive)
7682 && !inputs.getUIInteraction(player)->selectedItem && !players[player]->GUI.isDropdownActive() )
7683 {
7684 if ( bFrameCapturesMouse = statusEffectFrame->capturesMouse() )
7685 {
7686 if ( !players[player]->hud.statusFxFocusedWindowActive )
7687 {
7688 if ( (playerInventoryFrames[player].backpackFrame
7689 && !playerInventoryFrames[player].backpackFrame->isDisabled()
7690 && playerInventoryFrames[player].backpackFrame->capturesMouse())
7691 || (playerInventoryFrames[player].invSlotsFrame
7692 && playerInventoryFrames[player].invSlotsFrame->capturesMouse()) )
7693 {
7694 bFrameCapturesMouse = false;
7695 }
7696 }
7697 }
7698 }
7699 bool tooltipShowing = false;
7700 Sint32 mousex = (inputs.getMouse(player, Inputs::X) / (float)xres) * (float)Frame::virtualScreenX;
7701 Sint32 mousey = (inputs.getMouse(player, Inputs::Y) / (float)yres) * (float)Frame::virtualScreenY;
7702 bool lowDurationFlash = !((ticks % 50) - (ticks % 25));
7703
7704 if ( bCompactWidth != players[player]->bUseCompactGUIWidth() )
7705 {
7706 requiresAnimUpdate = true;
7707 }
7708 if ( bCompactHeight != players[player]->bUseCompactGUIHeight() )
7709 {
7710 requiresAnimUpdate = true;
7711 }
7712 bCompactWidth = players[player]->bUseCompactGUIWidth();
7713 bCompactHeight = players[player]->bUseCompactGUIHeight();
7714 effectsPerRow = 4;
7715 if ( bCompactWidth )
7716 {
7717 effectsPerRow = 3;
7718 }
7719
7720 int gridx = 1;
7721 int gridy = 0;
7722 std::map<int, StatusEffectQueueEntry_t*> grid;
7723 size_t index = 0;
7724 bool hungerEffectInEffectQueue = false;
7725 bool moduleActive = players[player]->GUI.activeModule == Player::GUI_t::MODULE_STATUS_EFFECTS;
7726 effectsBoundingBox = SDL_Rect{ 0, 0, 0, 0 };
7727 for ( auto it = effectQueue.rbegin(); it != effectQueue.rend(); )
7728 {
7729 auto& q = (*it);
7730
7731 q.index = index;
7732 ++index;
7733
7734 Frame::image_t* frameImg = nullptr;
7735 if ( frameImagesIterator != frameImages.end() )
7736 {
7737 frameImg = *frameImagesIterator;
7738 }
7739
7740 bool existsInNotifications = false;
7741 for ( auto it2 = notificationQueue.begin(); it2 != notificationQueue.end(); ++it2 )
7742 {
7743 if ( (*it2).effect == q.effect )
7744 {
7745 existsInNotifications = true;
7746 if ( effectsToSkipAnim.find(q.effect) != effectsToSkipAnim.end() )
7747 {
7748 updateEntryImage(q, frameImg);
7749 }
7750 break;
7751 }
7752 }
7753 if ( !existsInNotifications )
7754 {
7755 updateEntryImage(q, frameImg);
7756
7757 if ( q.effect == kEffectAutomatonHunger )
7758 {
7759 automatonHungerFrame->setDisabled(false);
7760 automatonHungerFrame->setSize(frameImg->pos);
7761 }
7762
7763 if ( bFrameCapturesMouse && !tooltipShowing )
7764 {
7765 SDL_Rect size = statusEffectFrame->getAbsoluteSize();
7766 int mouseDetectionPadding = 2;
7767 size.x += frameImg->pos.x - (mouseDetectionPadding);
7768 size.y += frameImg->pos.y - (mouseDetectionPadding);
7769 size.w = frameImg->pos.w + (mouseDetectionPadding * 2);
7770 size.h = frameImg->pos.h + (mouseDetectionPadding * 2);
7771 if ( rectContainsPoint(size, mousex, mousey) )
7772 {
7773 if ( players[player]->GUI.activeModule == Player::GUI_t::MODULE_STATUS_EFFECTS )
7774 {
7775 tooltipShowing = doStatusEffectTooltip(q, size);
7776 }
7777 players[player]->GUI.activateModule(Player::GUI_t::MODULE_STATUS_EFFECTS);
7778 players[player]->hud.setCursorDisabled(false);
7779
7780 // make sure to adjust absolute size to camera viewport
7781 size.x -= players[player]->camera_virtualx1();
7782 size.y -= players[player]->camera_virtualy1();
7783
7784 players[player]->hud.updateCursorAnimation(size.x - 1 + mouseDetectionPadding, size.y - 1 + mouseDetectionPadding,
7785 frameImg->pos.w, frameImg->pos.h, inputs.getVirtualMouse(player)->draw_cursor);
7786 }
7787 }
7788 }
7789 int animatePosX = movex;
7790 int animatePosY = movey;
7791 if ( q.effect == kEffectBread || q.effect == kEffectBloodHunger )
7792 {
7793 animatePosX = 0;
7794 animatePosY = statusEffectFrame->getSize().h - q.getEffectSpriteNormalHeight();
7795 }
7796 else if ( q.effect == kEffectAutomatonHunger )
7797 {
7798 animatePosX = 0;
7799 animatePosY = 4 + statusEffectFrame->getSize().h - q.getEffectSpriteNormalHeight();
7800 }
7801
7802 effectsBoundingBox.y = std::max(effectsBoundingBox.y, animatePosY + q.getEffectSpriteNormalHeight());
7803 effectsBoundingBox.w = std::max(effectsBoundingBox.w, animatePosX + q.getEffectSpriteNormalWidth());
7804 if ( effectsBoundingBox.h == 0 )
7805 {
7806 effectsBoundingBox.h = animatePosY;
7807 }
7808 else
7809 {
7810 effectsBoundingBox.h = std::min(effectsBoundingBox.h, animatePosY);
7811 }
7812
7813 // low duration flash
7814 bool effectIsSustained = false;
7815 if ( StatusEffectDefinitions_t::effectDefinitionExists(q.effect) )
7816 {
7817 auto& definition = StatusEffectDefinitions_t::getEffect(q.effect);
7818 if ( definition.sustainedSpellID >= 0 )
7819 {
7820 if ( spellsActive.find(definition.sustainedSpellID) != spellsActive.end() )
7821 {
7822 effectIsSustained = true;
7823 }
7824 }
7825 }
7826
7827 q.lowDuration = false;
7828 if ( !effectIsSustained && q.effect >= 0 && q.effect < NUMEFFECTS )
7829 {
7830 bool lowDuration = stats[player]->EFFECTS_TIMERS[q.effect] > 0 &&
7831 (stats[player]->EFFECTS_TIMERS[q.effect] < TICKS_PER_SECOND50 * 5);
7832 if ( q.effect == EFF_NAUSEA_PROTECTION )
7833 {
7834 lowDuration = false;
7835 }
7836 else if ( q.effect == EFF_BLIND && stats[player]->mask
7837 && (stats[player]->mask->type == TOOL_BLINDFOLD
7838 || stats[player]->mask->type == TOOL_BLINDFOLD_FOCUS
7839 || stats[player]->mask->type == TOOL_BLINDFOLD_TELEPATHY) )
7840 {
7841 lowDuration = false;
7842 }
7843 else if ( q.effect == EFF_TELEPATH && stats[player]->mask
7844 && (stats[player]->mask->type == TOOL_BLINDFOLD_TELEPATHY) )
7845 {
7846 lowDuration = false;
7847 }
7848 q.lowDuration = lowDuration;
7849 if ( lowDuration && lowDurationFlash )
7850 {
7851 frameImg->disabled = true;
7852 }
7853 }
7854 else if ( q.effect == kEffectBread || q.effect == kEffectBloodHunger || q.effect == kEffectAutomatonHunger )
7855 {
7856 if ( q.effect == kEffectAutomatonHunger )
7857 {
7858 if ( q.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_AUTOMATON_CRITICAL) )
7859 {
7860 q.lowDuration = true;
7861 if ( lowDurationFlash )
7862 {
7863 frameImg->disabled = true;
7864 automatonHungerFrame->setDisabled(true);
7865 }
7866 }
7867 }
7868 else
7869 {
7870 if ( q.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_WEAK) )
7871 {
7872 q.lowDuration = true;
7873 if ( lowDurationFlash )
7874 {
7875 frameImg->disabled = true;
7876 }
7877 }
7878 }
7879 }
7880 else if ( q.effect == kEffectBurning )
7881 {
7882 q.lowDuration = true;
7883 if ( lowDurationFlash )
7884 {
7885 frameImg->disabled = true;
7886 }
7887 }
7888 else if ( q.effect == kEffectPush )
7889 {
7890 q.lowDuration = false;
7891 if ( !((stats[player]->ring && stats[player]->ring->type == RING_STRENGTH)
7892 || (stats[player]->gloves && stats[player]->gloves->type == GAUNTLETS_STRENGTH)) )
7893 {
7894 bool lowDuration = stats[player]->EFFECTS_TIMERS[EFF_POTION_STR] > 0 &&
7895 (stats[player]->EFFECTS_TIMERS[EFF_POTION_STR] < TICKS_PER_SECOND50 * 5);
7896 q.lowDuration = lowDuration;
7897 if ( lowDurationFlash && lowDuration )
7898 {
7899 frameImg->disabled = true;
7900 }
7901 }
7902 }
7903
7904 if ( requiresAnimUpdate )
7905 {
7906 q.setAnimatePosition(animatePosX, animatePosY);
7907 }
7908 for ( auto it2 = notificationQueue.begin(); it2 != notificationQueue.end(); ++it2 )
7909 {
7910 if ( (*it2).effect == q.effect )
7911 {
7912 (*it2).notificationTargetPosition.x = animatePosX;
7913 (*it2).notificationTargetPosition.y = animatePosY;
7914 break;
7915 }
7916 }
7917
7918 ++it;
7919 if ( frameImagesIterator != frameImages.end() )
7920 {
7921 ++frameImagesIterator;
7922 }
7923
7924 q.animate();
7925
7926 if ( q.effect == kEffectBread || q.effect == kEffectBloodHunger || q.effect == kEffectAutomatonHunger )
7927 {
7928 assert(grid.find(0 + 0 * 10000) == grid.end())(static_cast<void> (0));
7929 grid[0 + 0 * 10000] = &q;
7930
7931 hungerEffectInEffectQueue = true;
7932 continue; // don't advance position as this is fixed
7933 }
7934
7935 assert(grid.find(gridx + gridy * 10000) == grid.end())(static_cast<void> (0));
7936 grid[gridx + gridy * 10000] = &q;
7937
7938
7939 ++gridx;
7940 movex += spacing;
7941 ++numEffectsOnLine;
7942 if ( numEffectsOnLine >= effectsPerRow )
7943 {
7944 numEffectsOnLine = 0;
7945 movex = startrowx;
7946 movey -= spacing;
7947 gridx = 1;
7948 ++gridy;
7949 }
7950 }
7951
7952 handleNavigation(grid, tooltipShowing, hungerEffectInEffectQueue);
7953
7954 if ( stats[player] && stats[player]->type == AUTOMATON && !automatonHungerFrame->isDisabled() )
7955 {
7956 SDL_Rect automatonHungerFramePos = automatonHungerFrame->getSize();
7957 SDL_Rect boilerFlamePos = automatonHungerFramePos;
7958 boilerFlamePos.x = 0;
7959 boilerFlamePos.y = 0;
7960 if ( stats[player]->HUNGER > 300 )
7961 {
7962 if ( stats[player]->HUNGER > 1200 )
7963 {
7964 automatonFlameImg->path = "images/system/Hunger_boiler_hotfire.png";
7965 automatonFlameImg->pos = boilerFlamePos;
7966 }
7967 else
7968 {
7969 automatonFlameImg->path = "images/system/Hunger_boiler_fire.png";
7970 if ( stats[player]->HUNGER > 600 )
7971 {
7972 automatonFlameImg->pos = boilerFlamePos;
7973 }
7974 else
7975 {
7976 float percent = (stats[player]->HUNGER - 200) / 400.f; // always show a little bit more at the bottom (10-20%)
7977 automatonFlameImg->pos = boilerFlamePos;
7978 int newHeight = boilerFlamePos.h * percent;
7979 int heightDiff = boilerFlamePos.h - newHeight;
7980 automatonFlameImg->pos.y -= heightDiff;
7981 automatonHungerFramePos.y += heightDiff;
7982 automatonHungerFramePos.h -= heightDiff;
7983 automatonHungerFrame->setSize(automatonHungerFramePos);
7984 }
7985 }
7986 }
7987 else
7988 {
7989 automatonHungerFrame->setDisabled(true);
7990 }
7991 }
7992
7993 animateStatusEffectTooltip(tooltipShowing);
7994
7995 if ( moduleActive && inputs.getVirtualMouse(player)->draw_cursor )
7996 {
7997 if ( !tooltipShowing /*&& !players[player]->hud.statusFxFocusedWindowActive*/ )
7998 {
7999 players[player]->GUI.activateModule(Player::GUI_t::MODULE_NONE);
8000 }
8001 }
8002
8003 requiresAnimUpdate = false;
8004}
8005
8006void StatusEffectQueue_t::updateEntryImage(StatusEffectQueueEntry_t& entry, Frame::image_t* img)
8007{
8008 if ( img )
8009 {
8010 img->path = "";
8011 if ( entry.effect == kEffectBread || entry.effect == kEffectBloodHunger )
8012 {
8013 if ( StatusEffectDefinitions_t::effectDefinitionExists(entry.effect) )
8014 {
8015 int variation = -1;
8016 if ( entry.customVariable >= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_OVERSATIATED) )
8017 {
8018 variation = 0;
8019 }
8020 else if ( entry.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_STARVING) )
8021 {
8022 variation = 3;
8023 }
8024 else if ( entry.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_WEAK) )
8025 {
8026 variation = 2;
8027 }
8028 else if ( entry.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_HUNGRY) )
8029 {
8030 variation = 1;
8031 }
8032 if ( variation >= 0 )
8033 {
8034 img->path = StatusEffectDefinitions_t::getEffectImgPath(StatusEffectDefinitions_t::getEffect(entry.effect), variation);
8035 }
8036 else
8037 {
8038 img->path = "";
8039 }
8040 }
8041 }
8042 else if ( entry.effect == kEffectAutomatonHunger )
8043 {
8044 int variation = 1;
8045 if ( entry.customVariable >= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_AUTOMATON_SUPERHEATED) )
8046 {
8047 variation = 0;
8048 }
8049 else if ( entry.customVariable <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_AUTOMATON_CRITICAL) )
8050 {
8051 variation = 2;
8052 }
8053 img->path = StatusEffectDefinitions_t::getEffectImgPath(StatusEffectDefinitions_t::getEffect(entry.effect), variation);
8054 }
8055 else
8056 {
8057 if ( entry.effect >= kSpellEffectOffset )
8058 {
8059 int effectID = entry.effect - kSpellEffectOffset;
8060 if ( StatusEffectDefinitions_t::sustainedSpellDefinitionExists(effectID) )
8061 {
8062 img->path = StatusEffectDefinitions_t::getEffectImgPath(StatusEffectDefinitions_t::getSustainedSpell(effectID));
8063 }
8064 }
8065 else
8066 {
8067 int effectID = entry.effect;
8068 if ( StatusEffectDefinitions_t::effectDefinitionExists(effectID) )
8069 {
8070 int variation = -1;
8071 if ( effectID == EFF_SHAPESHIFT )
8072 {
8073 if ( players[player] && players[player]->entity )
8074 {
8075 switch ( players[player]->entity->effectShapeshift )
8076 {
8077 case RAT:
8078 variation = 0;
8079 break;
8080 case SPIDER:
8081 variation = 1;
8082 break;
8083 case TROLL:
8084 variation = 2;
8085 break;
8086 case CREATURE_IMP:
8087 variation = 3;
8088 break;
8089 default:
8090 break;
8091 }
8092 }
8093 if ( variation == -1 )
8094 {
8095 img->path = "";
8096 }
8097 else
8098 {
8099 img->path = StatusEffectDefinitions_t::getEffectImgPath(StatusEffectDefinitions_t::getEffect(effectID), variation);
8100 }
8101 }
8102 else
8103 {
8104 img->path = StatusEffectDefinitions_t::getEffectImgPath(StatusEffectDefinitions_t::getEffect(effectID), variation);
8105 }
8106 }
8107 }
8108 }
8109
8110 SDL_Rect dest{ 0, 0, entry.pos.w, entry.pos.h };
8111 dest.x = entry.pos.x;
8112 dest.y = entry.pos.y;
8113 img->pos = dest;
8114 img->disabled = false;
8115
8116 if ( img->path.size() > 1 && img->path[0] != '*' )
8117 {
8118 img->path.insert(0, 1, '*');
8119 }
8120 }
8121}
8122
8123void updateStatusEffectQueue(const int player)
8124{
8125 auto& statusEffectQueue = StatusEffectQueue[player];
8126 Frame* statusEffectFrame = statusEffectQueue.getStatusEffectFrame();
8127 if ( !statusEffectFrame )
8128 {
8129 return;
8130 }
8131 if ( gamePaused && multiplayer == SINGLE0 )
8132 {
8133 return;
8134 }
8135 auto& hud_t = players[player]->hud;
8136 SDL_Rect mainFramePos{ 0, 0, players[player]->camera_virtualWidth(), players[player]->camera_virtualHeight() / 2 };
8137 mainFramePos.x = hud_t.hpFrame->getSize().x;
8138 mainFramePos.y = hud_t.hpFrame->getSize().y - mainFramePos.h;
8139 mainFramePos.w -= mainFramePos.x;
8140 if ( players[player]->hud.statusFxFocusedWindowActive )
8141 {
8142 mainFramePos.x += 8 * statusEffectQueue.focusedWindowAnim;
8143 mainFramePos.y -= 8 * statusEffectQueue.focusedWindowAnim;
8144 }
8145 statusEffectFrame->setSize(mainFramePos);
8146 auto innerFrame = statusEffectFrame->findFrame("effects");
8147 innerFrame->setSize(SDL_Rect{ 0, 0, statusEffectFrame->getSize().w, statusEffectFrame->getSize().h });
8148
8149 const int hungerEffectID = ((stats[player] && stats[player]->type == AUTOMATON) ? StatusEffectQueue_t::kEffectAutomatonHunger
8150 : (playerRequiresBloodToSustain(player) ? StatusEffectQueue_t::kEffectBloodHunger : StatusEffectQueue_t::kEffectBread));
8151
8152 // hunger icon
8153 if ( stats[player] && stats[player]->type != AUTOMATON )
8154 {
8155 statusEffectQueue.deleteEffect(StatusEffectQueue_t::kEffectAutomatonHunger);
8156 }
8157
8158 bool effectsEnabled = true;
8159 if ( !players[player]->entity && ((multiplayer == SINGLE0 && splitscreen) || multiplayer != SINGLE0) )
8160 {
8161 effectsEnabled = false;
8162 }
8163 if ( players[player]->ghost.isActive() && !kAllowGhostStatusEffects )
8164 {
8165 effectsEnabled = false;
8166 }
8167
8168 if ( effectsEnabled && stats[player] && stats[player]->type != AUTOMATON
8169 && (svFlags & SV_FLAG_HUNGER)
8170 && (stats[player]->HUNGER <= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_HUNGRY)
8171 || stats[player]->HUNGER >= getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_OVERSATIATED)) )
8172 {
8173 statusEffectQueue.deleteEffect(StatusEffectQueue_t::kEffectAutomatonHunger);
8174 if ( hungerEffectID == StatusEffectQueue_t::kEffectBloodHunger )
8175 {
8176 statusEffectQueue.deleteEffect(StatusEffectQueue_t::kEffectBread); // delete opposite if present
8177 }
8178 if ( hungerEffectID == StatusEffectQueue_t::kEffectBread )
8179 {
8180 statusEffectQueue.deleteEffect(StatusEffectQueue_t::kEffectBloodHunger); // delete opposite if present
8181 }
8182
8183 const int HUNGER_NONE = 1000;
8184 const int HUNGER_OVERSATIATED = getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_OVERSATIATED);
8185 const int HUNGER_HUNGRY = getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_HUNGRY);
8186 const int HUNGER_WEAK = getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_WEAK);
8187 const int HUNGER_STARVING = getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_STARVING);
8188 int hungerStateToSet = HUNGER_NONE;
8189 if ( stats[player]->HUNGER >= HUNGER_OVERSATIATED )
8190 {
8191 hungerStateToSet = HUNGER_OVERSATIATED;
8192 }
8193 else if ( stats[player]->HUNGER <= HUNGER_STARVING )
8194 {
8195 hungerStateToSet = HUNGER_STARVING;
8196 }
8197 else if ( stats[player]->HUNGER <= HUNGER_WEAK )
8198 {
8199 hungerStateToSet = HUNGER_WEAK;
8200 }
8201 else if ( stats[player]->HUNGER <= HUNGER_HUNGRY )
8202 {
8203 hungerStateToSet = HUNGER_HUNGRY;
8204 }
8205 StatusEffectQueueEntry_t* entry = nullptr;
8206 StatusEffectQueueEntry_t* notif = nullptr;
8207
8208
8209 for ( auto& q : statusEffectQueue.effectQueue )
8210 {
8211 if ( q.effect == hungerEffectID )
8212 {
8213 entry = &(q);
8214 for ( auto& n : statusEffectQueue.notificationQueue )
8215 {
8216 if ( n.effect == hungerEffectID )
8217 {
8218 notif = &(n);
8219 }
8220 break;
8221 }
8222 break;
8223 }
8224 }
8225 if ( entry && entry->customVariable != hungerStateToSet )
8226 {
8227 if ( notif && notif->customVariable != hungerStateToSet )
8228 {
8229 // reset the notification
8230 bool erased = false;
8231 for ( auto it = statusEffectQueue.notificationQueue.begin(); it != statusEffectQueue.notificationQueue.end(); ++it )
8232 {
8233 if ( (*it).effect == hungerEffectID )
8234 {
8235 statusEffectQueue.notificationQueue.erase(it);
8236 erased = true;
8237 break;
8238 }
8239 }
8240 statusEffectQueue.notificationQueue.push_back(StatusEffectQueueEntry_t(hungerEffectID));
8241 statusEffectQueue.notificationQueue.back().pos.x = statusEffectQueue.getBaseEffectPosX();
8242 statusEffectQueue.notificationQueue.back().pos.y = statusEffectQueue.getBaseEffectPosY();
8243
8244 // fall back if notificationTargetPosition doesn't have an effect to go to.
8245 statusEffectQueue.notificationQueue.back().notificationTargetPosition.x = 0;
8246 statusEffectQueue.notificationQueue.back().notificationTargetPosition.y = statusEffectQueue.statusEffectFrame->getSize().h
8247 - statusEffectQueue.notificationQueue.back().notificationTargetPosition.h;
8248 statusEffectQueue.requiresAnimUpdate = true;
8249 entry->customVariable = hungerStateToSet;
8250 statusEffectQueue.notificationQueue.back().customVariable = hungerStateToSet;
8251 }
8252 else
8253 {
8254 // else, delete and reapply
8255 statusEffectQueue.deleteEffect(hungerEffectID);
8256 if ( statusEffectQueue.insertEffect(hungerEffectID, -1) )
8257 {
8258 if ( statusEffectQueue.effectQueue.back().effect == hungerEffectID )
8259 {
8260 statusEffectQueue.effectQueue.back().customVariable = hungerStateToSet;
8261 statusEffectQueue.notificationQueue.back().customVariable = hungerStateToSet;
8262 }
8263 }
8264 }
8265 }
8266 else
8267 {
8268 // new effect, does not exist
8269 if ( statusEffectQueue.insertEffect(hungerEffectID, -1) )
8270 {
8271 if ( statusEffectQueue.effectQueue.back().effect == hungerEffectID )
8272 {
8273 statusEffectQueue.effectQueue.back().customVariable = hungerStateToSet;
8274 statusEffectQueue.notificationQueue.back().customVariable = hungerStateToSet;
8275 }
8276 }
8277 }
8278 }
8279 else if ( effectsEnabled )
8280 {
8281 statusEffectQueue.deleteEffect(StatusEffectQueue_t::kEffectBloodHunger);
8282 statusEffectQueue.deleteEffect(StatusEffectQueue_t::kEffectBread);
8283
8284 if ( hungerEffectID == StatusEffectQueue_t::kEffectAutomatonHunger )
8285 {
8286 const int HUNGER_NONE = 1000;
8287 const int HUNGER_SUPERHEATED = getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_AUTOMATON_SUPERHEATED);
8288 const int HUNGER_CRITICAL = getEntityHungerInterval(player, nullptr, stats[player], HUNGER_INTERVAL_AUTOMATON_CRITICAL);
8289 int hungerStateToSet = HUNGER_NONE;
8290 if ( stats[player]->HUNGER >= HUNGER_SUPERHEATED )
8291 {
8292 hungerStateToSet = HUNGER_SUPERHEATED;
8293 }
8294 else if ( stats[player]->HUNGER <= HUNGER_CRITICAL )
8295 {
8296 hungerStateToSet = HUNGER_CRITICAL;
8297 }
8298 StatusEffectQueueEntry_t* entry = nullptr;
8299 StatusEffectQueueEntry_t* notif = nullptr;
8300
8301 for ( auto& q : statusEffectQueue.effectQueue )
8302 {
8303 if ( q.effect == hungerEffectID )
8304 {
8305 entry = &(q);
8306 for ( auto& n : statusEffectQueue.notificationQueue )
8307 {
8308 if ( n.effect == hungerEffectID )
8309 {
8310 notif = &(n);
8311 }
8312 break;
8313 }
8314 break;
8315 }
8316 }
8317 if ( entry && entry->customVariable != hungerStateToSet )
8318 {
8319 if ( notif && notif->customVariable != hungerStateToSet )
8320 {
8321 // reset the notification
8322 bool erased = false;
8323 for ( auto it = statusEffectQueue.notificationQueue.begin(); it != statusEffectQueue.notificationQueue.end(); ++it )
8324 {
8325 if ( (*it).effect == hungerEffectID )
8326 {
8327 statusEffectQueue.notificationQueue.erase(it);
8328 erased = true;
8329 break;
8330 }
8331 }
8332 statusEffectQueue.notificationQueue.push_back(StatusEffectQueueEntry_t(hungerEffectID));
8333 statusEffectQueue.notificationQueue.back().pos.x = statusEffectQueue.getBaseEffectPosX();
8334 statusEffectQueue.notificationQueue.back().pos.y = statusEffectQueue.getBaseEffectPosY();
8335
8336 // fall back if notificationTargetPosition doesn't have an effect to go to.
8337 statusEffectQueue.notificationQueue.back().notificationTargetPosition.x = 0;
8338 statusEffectQueue.notificationQueue.back().notificationTargetPosition.y = statusEffectFrame->getSize().h
8339 - statusEffectQueue.notificationQueue.back().notificationTargetPosition.h;
8340 statusEffectQueue.requiresAnimUpdate = true;
8341 entry->customVariable = hungerStateToSet;
8342 statusEffectQueue.notificationQueue.back().customVariable = hungerStateToSet;
8343 }
8344 else
8345 {
8346 // else, delete and reapply
8347 statusEffectQueue.deleteEffect(hungerEffectID);
8348 if ( statusEffectQueue.insertEffect(hungerEffectID, -1) )
8349 {
8350 if ( statusEffectQueue.effectQueue.back().effect == hungerEffectID )
8351 {
8352 statusEffectQueue.effectQueue.back().customVariable = hungerStateToSet;
8353 statusEffectQueue.notificationQueue.back().customVariable = hungerStateToSet;
8354 }
8355 }
8356 }
8357 }
8358 else
8359 {
8360 // new effect, does not exist
8361 if ( statusEffectQueue.insertEffect(hungerEffectID, -1) )
8362 {
8363 if ( statusEffectQueue.effectQueue.back().effect == hungerEffectID )
8364 {
8365 statusEffectQueue.effectQueue.back().customVariable = hungerStateToSet;
8366 statusEffectQueue.notificationQueue.back().customVariable = hungerStateToSet;
8367 }
8368 }
8369 }
8370 }
8371 }
8372
8373 statusEffectQueue.updateAllQueuedEffects();
8374}
8375
8376void Player::Inventory_t::updateInventoryMiscTooltip()
8377{
8378 if ( !frame )
8379 {
8380 return;
8381 }
8382 if ( !miscTooltipFrame )
8383 {
8384 auto tooltipFrame = frame->addFrame("misc tooltip");
8385 miscTooltipFrame = tooltipFrame;
8386 tooltipFrame->setSize(SDL_Rect{ 212, 0, 200, 200 });
8387 tooltipFrame->setHollow(true);
8388 tooltipFrame->setInheritParentFrameOpacity(false);
8389 tooltipFrame->setDisabled(true);
8390 Uint32 color = makeColor(255, 255, 255, 255);
8391 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
8392 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TL_00.png", skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
8393 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
8394 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TR_00.png", skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
8395 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
8396 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_T_00.png", skillsheetEffectBackgroundImages[TOP].c_str());
8397 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
8398 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_L_00.png", skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str());
8399 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
8400 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_R_00.png", skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str());
8401 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
8402 makeColor(22, 24, 29, 255), "images/system/white.png", skillsheetEffectBackgroundImages[MIDDLE].c_str());
8403 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
8404 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_BL_00.png", skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str());
8405 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
8406 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_BR_00.png", skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str());
8407 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
8408 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_B_00.png", skillsheetEffectBackgroundImages[BOTTOM].c_str());
8409 imageSetWidthHeight9x9(tooltipFrame, skillsheetEffectBackgroundImages);
8410 imageResizeToContainer9x9(tooltipFrame, SDL_Rect{ 0, 0, 200, 200 }, skillsheetEffectBackgroundImages);
8411 auto txt = tooltipFrame->addField("tooltip text", 1024);
8412 const char* tooltipFont = "fonts/pixel_maz_multiline.ttf#16#2";
8413 txt->setFont(tooltipFont);
8414 txt->setColor(makeColor(188, 154, 114, 255));
8415 }
8416
8417 auto tooltipFrame = miscTooltipFrame;
8418 if ( tooltipFrame->isDisabled() )
8419 {
8420 miscTooltipOpacitySetpoint = 0;
8421 miscTooltipOpacityAnimate = 1.0;
8422 }
8423
8424 if ( static_cast<int>(tooltipFrame->getOpacity()) != miscTooltipOpacitySetpoint )
8425 {
8426 const real_t fpsScale = getFPSScale(144.0);
8427 if ( miscTooltipOpacitySetpoint == 0 )
8428 {
8429 if ( ticks - miscTooltipDeselectedTick > 5 )
8430 {
8431 real_t factor = 10.0;
8432 real_t setpointDiff = fpsScale * std::max(.05, (miscTooltipOpacityAnimate)) / (factor);
8433 miscTooltipOpacityAnimate -= setpointDiff;
8434 miscTooltipOpacityAnimate = std::max(0.0, miscTooltipOpacityAnimate);
8435 }
8436 }
8437 else
8438 {
8439 real_t setpointDiff = fpsScale * std::max(.05, (1.0 - miscTooltipOpacityAnimate)) / (1);
8440 miscTooltipOpacityAnimate += setpointDiff;
8441 miscTooltipOpacityAnimate = std::min(1.0, miscTooltipOpacityAnimate);
8442 }
8443 tooltipFrame->setOpacity(miscTooltipOpacityAnimate * 100);
8444 }
8445 else
8446 {
8447 tooltipFrame->setOpacity(miscTooltipOpacitySetpoint);
8448 }
8449
8450 Button* autosortBtn = nullptr;
8451 if ( player.shootmode )
8452 {
8453 miscTooltipOpacitySetpoint = 0;
8454 miscTooltipOpacityAnimate = 0.0;
8455 return;
8456 }
8457 if ( !playerInventoryFrames[player.playernum].autosortFrame )
8458 {
8459 return;
8460 }
8461 else
8462 {
8463 autosortBtn = playerInventoryFrames[player.playernum].autosortFrame->findButton("autosort button");
8464 if ( !autosortBtn || !autosortBtn->isHighlighted() || !autosortBtn->isSelected() || autosortBtn->isDisabled() )
8465 {
8466 return;
8467 }
8468 }
8469
8470 miscTooltipOpacitySetpoint = 0;
8471 miscTooltipOpacityAnimate = 1.0;
8472 tooltipFrame->setDisabled(false);
8473 tooltipFrame->setOpacity(100.0);
8474 miscTooltipDeselectedTick = ticks;
8475
8476 Uint32 defaultColor = hudColors.characterSheetNeutral;
8477 auto txt = tooltipFrame->findField("tooltip text");
8478 txt->setColor(defaultColor);
8479
8480 if ( true )
8481 {
8482 const int maxWidth = 200;
8483 const int padx = 16;
8484 const int pady1 = 8;
8485 const int pady2 = 8;
8486 const int padyMid = 4;
8487 const int padxMid = 4;
8488 SDL_Rect tooltipPos = SDL_Rect{ 400, 0, maxWidth, 100 };
8489 bool usingMouse = !inputs.getVirtualMouse(player.playernum)->lastMovementFromController;
8490 bool updateTxt = false;
8491 if ( player.bUseCompactGUIHeight() )
8492 {
8493 if ( strcmp(player.characterSheet.getHoverTextString("autosort_button_mouse_compact").c_str(), txt->getText()) )
8494 {
8495 txt->setText(player.characterSheet.getHoverTextString("autosort_button_mouse_compact").c_str());
8496 updateTxt = true;
8497 }
8498 }
8499 else
8500 {
8501 if ( strcmp(player.characterSheet.getHoverTextString("autosort_button_mouse").c_str(), txt->getText()) )
8502 {
8503 txt->setText(player.characterSheet.getHoverTextString("autosort_button_mouse").c_str());
8504 updateTxt = true;
8505 }
8506 }
8507
8508 SDL_Rect txtPos = SDL_Rect{ padx, pady1, maxWidth - padx * 2, 80 };
8509 txt->setSize(txtPos);
8510 Font* actualFont = Font::get(txt->getFont());
8511 int txtHeight = txt->getNumTextLines() * actualFont->height(true);
8512 txtPos.h = txtHeight + padyMid;
8513 auto txtGet = Text::get(txt->getLongestLine().c_str(), txt->getFont(),
8514 txt->getTextColor(), txt->getOutlineColor());
8515 txtPos.w = txtGet->getWidth();
8516 txt->setSize(txtPos);
8517
8518 tooltipPos.w = txtPos.w + padx * 2;
8519 tooltipPos.h = pady1 + txtPos.h + pady2;
8520
8521 if ( updateTxt )
8522 {
8523 txt->reflowTextToFit(0);
8524 }
8525
8526 int tooltipCoordX = 0;
8527 PanelJustify_t justify = paperDollPanelJustify;
8528 auto inventoryBgFrame = playerInventoryFrames[player.playernum].inventoryBgFrame;
8529 if ( !bCompactView )
8530 {
8531 Frame::image_t* invBaseImg = invBaseImg = playerInventoryFrames[player.playernum].defaultInvImg;
8532
8533 if ( justify == PANEL_JUSTIFY_LEFT )
8534 {
8535 tooltipCoordX = inventoryBgFrame->getSize().x + 8;
8536 tooltipCoordX += invBaseImg->pos.w;
8537 }
8538 else
8539 {
8540 tooltipCoordX = inventoryBgFrame->getSize().x - 8;
8541 tooltipCoordX += invBaseImg->pos.x;
8542 tooltipCoordX -= tooltipPos.w;
8543 }
8544 }
8545 else
8546 {
8547 Frame::image_t* compactImg = compactImg = playerInventoryFrames[player.playernum].compactCharImg;
8548 if ( justify == PANEL_JUSTIFY_LEFT )
8549 {
8550 tooltipCoordX = inventoryBgFrame->getSize().x + 8;
8551 tooltipCoordX += compactImg->pos.w;
8552 }
8553 else
8554 {
8555 tooltipCoordX = inventoryBgFrame->getSize().x - 8;
8556 tooltipCoordX += compactImg->pos.x;
8557 tooltipCoordX -= tooltipPos.w;
8558 }
8559 }
8560 tooltipPos.x = tooltipCoordX;
8561 tooltipPos.y = autosortBtn->getSize().y;
8562 tooltipFrame->setSize(tooltipPos);
8563 imageResizeToContainer9x9(tooltipFrame, SDL_Rect{ 0, 0, tooltipPos.w, tooltipPos.h },
8564 skillsheetEffectBackgroundImages);
8565 }
8566}
8567
8568void createWorldTooltipPrompts(const int player)
8569{
8570 auto& hud_t = players[player]->hud;
8571 auto& worldTooltipFrame = hud_t.worldTooltipFrame;
8572 worldTooltipFrame = hud_t.hudFrame->addFrame("world tooltip");
8573 worldTooltipFrame->setHollow(true);
8574 worldTooltipFrame->setBorder(0);
8575 worldTooltipFrame->setOwner(player);
8576 worldTooltipFrame->setSize(SDL_Rect{ 0, 0, 0, 0 });
8577 worldTooltipFrame->setDisabled(true);
8578
8579 const char* promptFont = "fonts/pixel_maz_multiline.ttf#16#2";
8580
8581 Uint32 iconColor = makeColor(255, 255, 255, Player::HUD_t::actionPromptIconOpacity);
8582 Uint32 iconBackingColor = makeColor(255, 255, 255, Player::HUD_t::actionPromptIconBackingOpacity);
8583
8584 auto text = worldTooltipFrame->addField("prompt text", 256);
8585 text->setFont(promptFont);
8586 text->setText("");
8587 text->setDisabled(true);
8588 text->setSize(SDL_Rect{ 0, 0, 0, 0 });
8589
8590 auto text2 = worldTooltipFrame->addField("prompt cycle text", 256);
8591 text2->setFont(promptFont);
8592 text2->setText("");
8593 text2->setDisabled(true);
8594 text2->setSize(SDL_Rect{ 0, 0, 0, 0 });
8595
8596 auto text3 = worldTooltipFrame->addField("prompt callout text", 256);
8597 text3->setFont(promptFont);
8598 text3->setText("");
8599 text3->setDisabled(true);
8600 text3->setSize(SDL_Rect{ 0, 0, 0, 0 });
8601
8602 const int iconSize = 24;
8603 SDL_Rect iconPos{ 0, 0, iconSize, iconSize };
8604
8605 auto icon = worldTooltipFrame->addImage(iconPos,
8606 iconColor, "images/system/white.png", "icon img");
8607 icon->disabled = true;
8608
8609 auto glyph = worldTooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
8610 0xFFFFFFFF, "images/system/white.png", "glyph img");
8611 glyph->disabled = true;
8612
8613 auto glyphAdditional = worldTooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
8614 0xFFFFFFFF, "images/system/white.png", "glyph img 2");
8615 glyphAdditional->disabled = true;
8616
8617 auto glyphAdditional2 = worldTooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
8618 0xFFFFFFFF, "images/system/white.png", "glyph img 3");
8619 glyphAdditional2->disabled = true;
8620
8621 auto glyphAdditional3 = worldTooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
8622 0xFFFFFFFF, "images/system/white.png", "glyph img 4");
8623 glyphAdditional3->disabled = true;
8624
8625 auto cursor = worldTooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
8626 0xFFFFFFFF, "images/system/white.png", "cursor img");
8627 cursor->disabled = true;
8628}
8629
8630const char* Player::HUD_t::getCrosshairPath()
8631{
8632 switch ( playerSettings[multiplayer ? 0 : player.playernum].shootmodeCrosshair )
8633 {
8634 case 0:
8635 default:
8636 return "*#images/ui/Crosshairs/cross.png";
8637 break;
8638 case 1:
8639 return "*#images/ui/Crosshairs/cursor_thicc.png";
8640 break;
8641 case 2:
8642 return "*#images/ui/Crosshairs/cursor_plusA.png";
8643 break;
8644 case 3:
8645 return "*#images/ui/Crosshairs/cursor_plusB.png";
8646 break;
8647 case 4:
8648 return "*#images/ui/Crosshairs/cursor_crosshair.png";
8649 break;
8650 case 5:
8651 return "*#images/ui/Crosshairs/cursor_xB.png";
8652 break;
8653 case 6:
8654 return "*#images/ui/Crosshairs/cursor_carrot.png";
8655 break;
8656 case 7:
8657 return "*#images/ui/Crosshairs/cursor_circleB.png";
8658 break;
8659 case 8:
8660 return "*#images/ui/Crosshairs/cursor_dotsB.png";
8661 break;
8662 case 9:
8663 return "*#images/ui/Crosshairs/cursor_dotsC.png";
8664 break;
8665 case 10:
8666 return "*#images/ui/Crosshairs/cursor_smiley.png";
8667 break;
8668 case 11:
8669 return "*#images/ui/Crosshairs/cursor_nethack.png";
8670 break;
8671 }
8672}
8673
8674void Player::HUD_t::updateWorldTooltipPrompts()
8675{
8676 if ( !hudFrame )
8677 {
8678 return;
8679 }
8680
8681 if ( !worldTooltipFrame )
8682 {
8683 createWorldTooltipPrompts(player.playernum);
8684 if ( !worldTooltipFrame )
8685 {
8686 return;
8687 }
8688 }
8689
8690 worldTooltipFrame->setDisabled(true);
8691
8692 if ( !player.shootmode || nohud || gamePaused )
8693 {
8694 return;
8695 }
8696
8697 SDL_Rect promptPos{ player.camera_virtualWidth() / 2, player.camera_virtualHeight() / 2, 0, 0 };
8698
8699 FollowerRadialMenu& followerMenu = FollowerMenu[player.playernum];
8700 CalloutRadialMenu& calloutMenu = CalloutMenu[player.playernum];
8701
8702 auto icon = worldTooltipFrame->findImage("icon img");
8703 icon->disabled = true;
8704 auto glyph = worldTooltipFrame->findImage("glyph img");
8705 glyph->disabled = true;
8706 auto glyphAdditional = worldTooltipFrame->findImage("glyph img 2");
8707 glyphAdditional->disabled = true;
8708 auto cursor = worldTooltipFrame->findImage("cursor img");
8709 cursor->disabled = true;
8710 auto text = worldTooltipFrame->findField("prompt text");
8711 text->setDisabled(true);
8712 auto textCycle = worldTooltipFrame->findField("prompt cycle text");
8713 textCycle->setDisabled(true);
8714 auto textCallout = worldTooltipFrame->findField("prompt callout text");
8715 textCallout->setDisabled(true);
8716 auto glyphCycle = worldTooltipFrame->findImage("glyph img 3");
8717 glyphCycle->disabled = true;
8718 auto glyphCallout = worldTooltipFrame->findImage("glyph img 4");
8719 glyphCallout->disabled = true;
8720
8721 SDL_Rect textPos{ 0, 0, 0, 0 };
8722 const int skillIconToGlyphPadding = 4;
8723 const int nominalGlyphHeight = 26;
8724
8725 bool usingTinkeringKit = false;
8726 bool useBracketsReticle = false;
8727 bool useSneakingReticle = false;
8728 if ( player.entity && stats[player.playernum] ) {
8729 if ( stats[player.playernum]->defending ) {
8730 auto shield = stats[player.playernum]->shield;
8731 if ( shield && shield->type == TOOL_TINKERING_KIT ) {
8732 useBracketsReticle = true;
8733 usingTinkeringKit = true;
8734 }
8735 }
8736 if ( stats[player.playernum]->sneaking ) {
8737 useBracketsReticle = true;
8738 useSneakingReticle = true;
8739 }
8740 }
8741 else if ( player.ghost.isActive() )
8742 {
8743 if ( player.ghost.my->skill[3] == 1 )
8744 {
8745 useSneakingReticle = true;
8746 }
8747 }
8748
8749 bool followerInteract = followerMenu.selectMoveTo && (followerMenu.optionSelected == ALLY_CMD_MOVETO_SELECT
8750 || followerMenu.optionSelected == ALLY_CMD_ATTACK_SELECT);
8751 bool calloutInteract = calloutMenu.selectMoveTo && (calloutMenu.optionSelected == CalloutRadialMenu::CALLOUT_CMD_SELECT);
8752
8753 if ( followerInteract || calloutInteract )
8754 {
8755 bool forceBlankInteractText = false;
8756 auto optionSelected = followerInteract ? followerMenu.optionSelected : calloutMenu.optionSelected;
8757 if ( !player.worldUI.isEnabled() )
8758 {
8759 cursor->path = "#*images/ui/Crosshairs/cursor_xB.png";
8760 if ( auto imgGet = Image::get(cursor->path.c_str()) )
8761 {
8762 cursor->disabled = false;
8763 promptPos.x -= (int)imgGet->getWidth() / 2;
8764 promptPos.y -= (int)imgGet->getHeight() / 2;
8765 SDL_Rect cursorPos{ 0, 0, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
8766 cursor->pos = cursorPos;
8767 cursor->color = makeColor(255, 255, 255, 191);
8768 }
8769 textPos.x = cursor->pos.x + 24;
8770 textPos.y = cursor->pos.y + 20;
8771 }
8772 else
8773 {
8774 if ( optionSelected == ALLY_CMD_MOVETO_SELECT )
8775 {
8776 cursor->path = "#*images/ui/Crosshairs/cursor_xB.png";
8777 if ( auto imgGet = Image::get(cursor->path.c_str()) )
8778 {
8779 cursor->disabled = false;
8780 promptPos.x -= (int)imgGet->getWidth() / 2;
8781 promptPos.y -= (int)imgGet->getHeight() / 2;
8782 SDL_Rect cursorPos{ 0, 0, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
8783 cursor->pos = cursorPos;
8784 cursor->color = makeColor(255, 255, 255, 191);
8785 }
8786
8787 textPos.x = cursor->pos.x + cursor->pos.w / 2;
8788 textPos.y = cursor->pos.y + cursor->pos.h / 2;
8789 if ( auto imgGet = Image::get("images/system/selectedcursor.png") )
8790 {
8791 textPos.x -= (int)imgGet->getWidth() / 2;
8792 textPos.y -= (int)imgGet->getHeight() / 2;
8793 }
8794
8795 if ( textPos.x < 0 )
8796 {
8797 promptPos.x += textPos.x;
8798 cursor->pos.x -= textPos.x;
8799 textPos.x = 0;
8800 }
8801 if ( textPos.y < 0 )
8802 {
8803 promptPos.y += textPos.y;
8804 cursor->pos.y -= textPos.y;
8805 textPos.y = 0;
8806 }
8807 }
8808 else
8809 {
8810 if ( player.worldUI.isEnabled() && !player.worldUI.bTooltipInView )
8811 {
8812 forceBlankInteractText = true;
8813
8814 cursor->path = getCrosshairPath();
8815 if ( auto imgGet = Image::get(cursor->path.c_str()) )
8816 {
8817 cursor->disabled = false;
8818 promptPos.x -= (int)imgGet->getWidth() / 2;
8819 promptPos.y -= (int)imgGet->getHeight() / 2;
8820 SDL_Rect cursorPos{ 0, 0, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
8821 cursor->pos = cursorPos;
8822 cursor->color = makeColor(255, 255, 255, 255 * playerSettings[multiplayer ? 0 : player.playernum].shootmodeCrosshairOpacity / 100.f);
8823 }
8824
8825 textPos.x = cursor->pos.x + cursor->pos.w / 2;
8826 textPos.y = cursor->pos.y + cursor->pos.h / 2;
8827 if ( auto imgGet = Image::get("images/system/selectedcursor.png") )
8828 {
8829 textPos.x -= (int)imgGet->getWidth() / 2;
8830 textPos.y -= (int)imgGet->getHeight() / 2;
8831 }
8832
8833 if ( textPos.x < 0 )
8834 {
8835 promptPos.x += textPos.x;
8836 cursor->pos.x -= textPos.x;
8837 textPos.x = 0;
8838 }
8839 if ( textPos.y < 0 )
8840 {
8841 promptPos.y += textPos.y;
8842 cursor->pos.y -= textPos.y;
8843 textPos.y = 0;
8844 }
8845 }
8846 else
8847 {
8848 cursor->path = "images/system/selectedcursor.png";
8849 if ( auto imgGet = Image::get(cursor->path.c_str()) )
8850 {
8851 cursor->disabled = false;
8852 promptPos.x -= (int)imgGet->getWidth() / 2;
8853 promptPos.y -= (int)imgGet->getHeight() / 2;
8854 SDL_Rect cursorPos{ 0, 0, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
8855 cursor->pos = cursorPos;
8856 cursor->color = makeColor(255, 255, 255, 255 * playerSettings[multiplayer ? 0 : player.playernum].shootmodeCrosshairOpacity / 100.f);
8857 }
8858 }
8859 }
8860
8861 textPos.x += 40;
8862 textPos.y += 20;
8863 }
8864
8865 auto glyphPathPressed = Input::inputs[player.playernum].getGlyphPathForBinding("Use", true);
8866 auto glyphPathUnpressed = Input::inputs[player.playernum].getGlyphPathForBinding("Use", false);
8867 auto glyphAdditionalPathPressed = Input::inputs[player.playernum].getGlyphPathForBinding("Defend", true);
8868 auto glyphAdditionalPathUnpressed = Input::inputs[player.playernum].getGlyphPathForBinding("Defend", false);
8869 if ( ticks % 50 < 25 )
8870 {
8871 glyph->path = glyphPathPressed;
8872 if ( auto imgGet = Image::get(glyph->path.c_str()) )
8873 {
8874 glyph->disabled = false;
8875 SDL_Rect glyphPos{ textPos.x, textPos.y, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
8876 glyph->pos = glyphPos;
8877 if ( auto imgGetUnpressed = Image::get(glyphPathUnpressed.c_str()) )
8878 {
8879 const int unpressedHeight = imgGetUnpressed->getHeight();
8880 if ( unpressedHeight != glyph->pos.h )
8881 {
8882 glyph->pos.y -= (glyph->pos.h - unpressedHeight);
8883 }
8884
8885 if ( unpressedHeight != nominalGlyphHeight )
8886 {
8887 glyph->pos.y -= (unpressedHeight - nominalGlyphHeight) / 2;
8888 }
8889 }
8890 textPos.x += glyph->pos.w;
8891 }
8892 }
8893 else
8894 {
8895 glyph->path = glyphPathUnpressed;
8896 if ( auto imgGet = Image::get(glyph->path.c_str()) )
8897 {
8898 glyph->disabled = false;
8899 SDL_Rect glyphPos{ textPos.x, textPos.y, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
8900 glyph->pos = glyphPos;
8901 textPos.x += glyph->pos.w;
8902
8903 if ( glyph->pos.h != nominalGlyphHeight )
8904 {
8905 glyph->pos.y -= (glyph->pos.h - nominalGlyphHeight) / 2;
8906 }
8907 }
8908 }
8909
8910 textPos.x += skillIconToGlyphPadding;
8911
8912 if ( Input::inputs[player.playernum].binary("Defend") )
8913 {
8914 glyphAdditional->path = glyphAdditionalPathUnpressed;
8915 if ( auto imgGet = Image::get(glyphAdditional->path.c_str()) )
8916 {
8917 glyphAdditional->disabled = false;
8918 SDL_Rect glyphPos{ textPos.x, textPos.y, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
8919 glyphAdditional->pos = glyphPos;
8920 if ( auto imgGetUnpressed = Image::get(glyphAdditionalPathUnpressed.c_str()) )
8921 {
8922 const int unpressedHeight = imgGetUnpressed->getHeight();
8923 if ( unpressedHeight != glyphAdditional->pos.h )
8924 {
8925 glyphAdditional->pos.y -= (glyphAdditional->pos.h - unpressedHeight);
8926 }
8927
8928 if ( unpressedHeight != nominalGlyphHeight )
8929 {
8930 glyphAdditional->pos.y -= (unpressedHeight - nominalGlyphHeight) / 2;
8931 }
8932 }
8933 textPos.x += glyphAdditional->pos.w;
8934 textPos.x += skillIconToGlyphPadding;
8935 }
8936 }
8937
8938 if ( calloutInteract )
8939 {
8940 icon->path = "*images/ui/HUD/HUD_Ally_Callout_00.png";
8941 }
8942 else
8943 {
8944 for ( auto& skill : player.skillSheet.skillSheetData.skillEntries )
8945 {
8946 if ( skill.skillId == PRO_LEADERSHIP )
8947 {
8948 if ( skillCapstoneUnlocked(player.playernum, PRO_LEADERSHIP) )
8949 {
8950 icon->path = skill.skillIconPathLegend;
8951 }
8952 else
8953 {
8954 icon->path = skill.skillIconPath;
8955 }
8956 break;
8957 }
8958 }
8959 }
8960
8961 if ( auto imgGet = Image::get(icon->path.c_str()) )
8962 {
8963 icon->disabled = false;
8964 SDL_Rect iconPos{ textPos.x, textPos.y, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
8965 icon->pos = iconPos;
8966 textPos.x += icon->pos.w + skillIconToGlyphPadding;
8967 }
8968
8969
8970 if ( followerInteract && optionSelected == ALLY_CMD_MOVETO_SELECT )
8971 {
8972 if ( followerMenu.followerToCommand
8973 && (followerMenu.followerToCommand->getMonsterTypeFromSprite() == SENTRYBOT
8974 || followerMenu.followerToCommand->getMonsterTypeFromSprite() == SPELLBOT)
8975 )
8976 {
8977 text->setDisabled(false);
8978 if ( !glyphAdditional->disabled )
8979 {
8980 std::string txt = Language::get(4201);
8981 txt += Language::get(3650);
8982 text->setText(txt.c_str());
8983 }
8984 else
8985 {
8986 text->setText(Language::get(3650));
8987 }
8988 }
8989 else
8990 {
8991 text->setDisabled(false);
8992 if ( !glyphAdditional->disabled )
8993 {
8994 std::string txt = Language::get(4201);
8995 txt += Language::get(3039);
8996 text->setText(txt.c_str());
8997 }
8998 else
8999 {
9000 text->setText(Language::get(3039));
9001 }
9002 }
9003 }
9004 else
9005 {
9006 if ( followerInteract )
9007 {
9008 if ( !strcmp(followerMenu.interactText, "") || forceBlankInteractText )
9009 {
9010 if ( followerMenu.followerToCommand )
9011 {
9012 int type = followerMenu.followerToCommand->getMonsterTypeFromSprite();
9013 if ( followerMenu.allowedInteractItems(type)
9014 || followerMenu.allowedInteractFood(type)
9015 || followerMenu.allowedInteractWorld(type)
9016 )
9017 {
9018 text->setDisabled(false);
9019 text->setText(Language::get(4041)); // "Interact with..."
9020 }
9021 else
9022 {
9023 text->setDisabled(false);
9024 text->setText(Language::get(4042)); // "Attack..."
9025 }
9026 }
9027 else
9028 {
9029 text->setDisabled(false);
9030 text->setText(Language::get(4041)); // "Interact with..."
9031 }
9032 }
9033 else
9034 {
9035 text->setDisabled(false);
9036 text->setText(followerMenu.interactText);
9037 }
9038 }
9039 else if ( calloutInteract )
9040 {
9041 if ( !strcmp(calloutMenu.interactText, "") || forceBlankInteractText )
9042 {
9043 text->setDisabled(false);
9044 text->setText(Language::get(4348)); // "Call out..."
9045 }
9046 else
9047 {
9048 text->setDisabled(false);
9049 text->setText(calloutMenu.interactText);
9050 }
9051 }
9052 }
9053
9054 if ( !glyphAdditional->disabled )
9055 {
9056 if ( !strstr(text->getText(), Language::get(4201)) ) // no "(ALL) " text
9057 {
9058 glyphAdditional->disabled = true;
9059 textPos.x -= glyphAdditional->pos.w + skillIconToGlyphPadding;
9060 icon->pos.x -= glyphAdditional->pos.w + skillIconToGlyphPadding;
9061 //textPos.x -= icon->pos.w + skillIconToGlyphPadding;
9062 }
9063 }
9064 }
9065 else
9066 {
9067 if ( !player.entity && !player.ghost.isActive() )
9068 {
9069 cursor->disabled = true;
9070 }
9071 else if ( player.worldUI.bTooltipInView )
9072 {
9073 cursor->path = "images/system/selectedcursor.png";
9074 if ( auto imgGet = Image::get(cursor->path.c_str()) )
9075 {
9076 cursor->disabled = false;
9077 promptPos.x -= (int)imgGet->getWidth() / 2;
9078 promptPos.y -= (int)imgGet->getHeight() / 2;
9079 SDL_Rect cursorPos{ 0, 0, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
9080 cursor->pos = cursorPos;
9081 cursor->color = makeColor(255, 255, 255, 255 * playerSettings[multiplayer ? 0 : player.playernum].shootmodeCrosshairOpacity / 100.f);
9082 }
9083
9084 textPos.x += 40;
9085 textPos.y += 20;
9086
9087 auto glyphPathPressed = Input::inputs[player.playernum].getGlyphPathForBinding("Use", true);
9088 auto glyphPathUnpressed = Input::inputs[player.playernum].getGlyphPathForBinding("Use", false);
9089 if ( ticks % 50 < 25 )
9090 {
9091 glyph->path = glyphPathPressed;
9092 if ( auto imgGet = Image::get(glyph->path.c_str()) )
9093 {
9094 glyph->disabled = false;
9095 SDL_Rect glyphPos{ textPos.x, textPos.y, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
9096 glyph->pos = glyphPos;
9097 if ( auto imgGetUnpressed = Image::get(glyphPathUnpressed.c_str()) )
9098 {
9099 const int unpressedHeight = imgGetUnpressed->getHeight();
9100 if ( unpressedHeight != glyph->pos.h )
9101 {
9102 glyph->pos.y -= (glyph->pos.h - unpressedHeight);
9103 }
9104
9105 if ( unpressedHeight != nominalGlyphHeight )
9106 {
9107 glyph->pos.y -= (unpressedHeight - nominalGlyphHeight) / 2;
9108 }
9109 }
9110 textPos.x += glyph->pos.w;
9111 }
9112 }
9113 else
9114 {
9115 glyph->path = glyphPathUnpressed;
9116 if ( auto imgGet = Image::get(glyph->path.c_str()) )
9117 {
9118 glyph->disabled = false;
9119 SDL_Rect glyphPos{ textPos.x, textPos.y, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
9120 glyph->pos = glyphPos;
9121 textPos.x += glyph->pos.w;
9122
9123 if ( glyph->pos.h != nominalGlyphHeight )
9124 {
9125 glyph->pos.y -= (glyph->pos.h - nominalGlyphHeight) / 2;
9126 }
9127 }
9128 }
9129 textPos.x += skillIconToGlyphPadding;
9130
9131 if ( usingTinkeringKit )
9132 {
9133 for ( auto& skill : player.skillSheet.skillSheetData.skillEntries )
9134 {
9135 if ( skill.skillId == PRO_LOCKPICKING )
9136 {
9137 if ( skillCapstoneUnlocked(player.playernum, PRO_LOCKPICKING) )
9138 {
9139 icon->path = skill.skillIconPathLegend;
9140 }
9141 else
9142 {
9143 icon->path = skill.skillIconPath;
9144 }
9145 break;
9146 }
9147 }
9148
9149 if ( auto imgGet = Image::get(icon->path.c_str()) )
9150 {
9151 icon->disabled = false;
9152 SDL_Rect iconPos{ textPos.x, textPos.y, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
9153 icon->pos = iconPos;
9154 textPos.x += icon->pos.w + skillIconToGlyphPadding;
9155 }
9156 }
9157
9158 text->setDisabled(false);
9159 text->setText(player.worldUI.interactText.c_str());
9160 }
9161 else
9162 {
9163 if ( useSneakingReticle )
9164 {
9165 cursor->path = "*#images/system/sneakingcursor.png";
9166 }
9167 else if ( useBracketsReticle )
9168 {
9169 cursor->path = "*#images/system/selectedcursor.png";
9170 }
9171 else
9172 {
9173 cursor->path = getCrosshairPath();
9174 }
9175 if ( auto imgGet = Image::get(cursor->path.c_str()) )
9176 {
9177 cursor->disabled = false;
9178 promptPos.x -= (int)imgGet->getWidth() / 2;
9179 promptPos.y -= (int)imgGet->getHeight() / 2;
9180 SDL_Rect cursorPos{ 0, 0, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
9181 cursor->pos = cursorPos;
9182 cursor->color = makeColor(255, 255, 255, 255 * playerSettings[multiplayer ? 0 : player.playernum].shootmodeCrosshairOpacity / 100.f);
9183 }
9184
9185 if ( usingTinkeringKit )
9186 {
9187 if ( player.worldUI.isEnabled() )
9188 {
9189 textPos.x = cursor->pos.x + cursor->pos.w / 2;
9190 textPos.y = cursor->pos.y + cursor->pos.h / 2;
9191 if ( auto imgGet = Image::get("images/system/selectedcursor.png") )
9192 {
9193 textPos.x -= (int)imgGet->getWidth() / 2;
9194 textPos.y -= (int)imgGet->getHeight() / 2;
9195 }
9196
9197 if ( textPos.x < 0 )
9198 {
9199 promptPos.x += textPos.x;
9200 cursor->pos.x -= textPos.x;
9201 textPos.x = 0;
9202 }
9203 if ( textPos.y < 0 )
9204 {
9205 promptPos.y += textPos.y;
9206 cursor->pos.y -= textPos.y;
9207 textPos.y = 0;
9208 }
9209
9210 // skip cursor here, no tooltip in view
9211 textPos.x += 40;
9212 textPos.y += 20;
9213
9214 auto glyphPathPressed = Input::inputs[player.playernum].getGlyphPathForBinding("Use", true);
9215 auto glyphPathUnpressed = Input::inputs[player.playernum].getGlyphPathForBinding("Use", false);
9216 if ( ticks % 50 < 25 )
9217 {
9218 glyph->path = glyphPathPressed;
9219 if ( auto imgGet = Image::get(glyph->path.c_str()) )
9220 {
9221 glyph->disabled = false;
9222 SDL_Rect glyphPos{ textPos.x, textPos.y, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
9223 glyph->pos = glyphPos;
9224 if ( auto imgGetUnpressed = Image::get(glyphPathUnpressed.c_str()) )
9225 {
9226 const int unpressedHeight = imgGetUnpressed->getHeight();
9227 if ( unpressedHeight != glyph->pos.h )
9228 {
9229 glyph->pos.y -= (glyph->pos.h - unpressedHeight);
9230 }
9231
9232 if ( unpressedHeight != nominalGlyphHeight )
9233 {
9234 glyph->pos.y -= (unpressedHeight - nominalGlyphHeight) / 2;
9235 }
9236 }
9237 textPos.x += glyph->pos.w;
9238 }
9239 }
9240 else
9241 {
9242 glyph->path = glyphPathUnpressed;
9243 if ( auto imgGet = Image::get(glyph->path.c_str()) )
9244 {
9245 glyph->disabled = false;
9246 SDL_Rect glyphPos{ textPos.x, textPos.y, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
9247 glyph->pos = glyphPos;
9248 textPos.x += glyph->pos.w;
9249
9250 if ( glyph->pos.h != nominalGlyphHeight )
9251 {
9252 glyph->pos.y -= (glyph->pos.h - nominalGlyphHeight) / 2;
9253 }
9254 }
9255 }
9256 textPos.x += skillIconToGlyphPadding;
9257
9258 for ( auto& skill : player.skillSheet.skillSheetData.skillEntries )
9259 {
9260 if ( skill.skillId == PRO_LOCKPICKING )
9261 {
9262 if ( skillCapstoneUnlocked(player.playernum, PRO_LOCKPICKING) )
9263 {
9264 icon->path = skill.skillIconPathLegend;
9265 }
9266 else
9267 {
9268 icon->path = skill.skillIconPath;
9269 }
9270 break;
9271 }
9272 }
9273
9274 if ( auto imgGet = Image::get(icon->path.c_str()) )
9275 {
9276 icon->disabled = false;
9277 SDL_Rect iconPos{ textPos.x, textPos.y, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
9278 icon->pos = iconPos;
9279 textPos.x += icon->pos.w + skillIconToGlyphPadding;
9280 }
9281
9282 text->setDisabled(false);
9283 text->setText(Language::get(3663));
9284 }
9285 else
9286 {
9287 textPos.x = cursor->pos.x + 15;
9288 textPos.y = cursor->pos.y + 11;
9289
9290 text->setDisabled(false);
9291 text->setText(Language::get(3663));
9292 }
9293 }
9294 }
9295 }
9296
9297 if ( !text->isDisabled() )
9298 {
9299 textPos.w = text->getTextObject()->getWidth();
9300 textPos.h = Font::get(text->getFont())->height() + 8;
9301 textPos.y -= 1;
9302 text->setVJustify(Field::justify_t::CENTER);
9303 text->setSize(textPos);
9304
9305 promptPos.w = text->getSize().x + text->getSize().w;
9306 promptPos.h = text->getSize().y + text->getSize().h;
9307 }
9308 if ( !glyph->disabled )
9309 {
9310 promptPos.w = std::max(glyph->pos.x + glyph->pos.w, promptPos.w);
9311 promptPos.h = std::max(glyph->pos.y + glyph->pos.h, promptPos.h);
9312 }
9313 if ( !icon->disabled )
9314 {
9315 if ( icon->pos.h != nominalGlyphHeight )
9316 {
9317 icon->pos.y -= (icon->pos.h - nominalGlyphHeight) / 2;
9318 }
9319
9320 promptPos.w = std::max(icon->pos.x + icon->pos.w, promptPos.w);
9321 promptPos.h = std::max(icon->pos.y + icon->pos.h, promptPos.h);
9322 }
9323 if ( !cursor->disabled )
9324 {
9325 promptPos.w = std::max(cursor->pos.x + cursor->pos.w, promptPos.w);
9326 promptPos.h = std::max(cursor->pos.y + cursor->pos.h, promptPos.h);
9327 }
9328
9329 auto prevGlyphPos = glyph->pos;
9330 if ( false && !text->isDisabled() && !glyph->disabled )
9331 {
9332 if ( CalloutMenu[player.playernum].calloutMenuIsOpen() )
9333 {
9334 auto glyphPathUnpressed = Input::inputs[player.playernum].getGlyphPathForBinding("Call Out", false);
9335 auto glyphPathPressed = Input::inputs[player.playernum].getGlyphPathForBinding("Call Out", true);
9336
9337 if ( ticks % 50 < 25 )
9338 {
9339 glyphCallout->path = glyphPathPressed;
9340 if ( auto imgGet = Image::get(glyphCallout->path.c_str()) )
9341 {
9342 glyphCallout->disabled = false;
9343 glyphCallout->pos = SDL_Rect{ 0, 0, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
9344 glyphCallout->pos.y = std::max(icon->pos.y + icon->pos.h - 4, prevGlyphPos.y + prevGlyphPos.h) + 4;
9345 glyphCallout->pos.x = prevGlyphPos.x + prevGlyphPos.w / 2 - glyphCallout->pos.w / 2;
9346 if ( glyphCallout->pos.x % 2 == 1 )
9347 {
9348 ++glyphCallout->pos.x;
9349 }
9350 if ( auto imgGetUnpressed = Image::get(glyphPathUnpressed.c_str()) )
9351 {
9352 const int unpressedHeight = imgGetUnpressed->getHeight();
9353 if ( unpressedHeight != glyphCallout->pos.h )
9354 {
9355 glyphCallout->pos.y -= (glyphCallout->pos.h - unpressedHeight);
9356 }
9357
9358 /*if ( unpressedHeight != nominalGlyphHeight )
9359 {
9360 prevGlyphPos.y -= (unpressedHeight - nominalGlyphHeight) / 2;
9361 }*/
9362 }
9363 textPos.x += prevGlyphPos.w;
9364 }
9365 }
9366 else
9367 {
9368 glyphCallout->path = glyphPathUnpressed;
9369 if ( auto imgGet = Image::get(glyphCallout->path.c_str()) )
9370 {
9371 glyphCallout->disabled = false;
9372 glyphCallout->pos = SDL_Rect{ 0, 0, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
9373 glyphCallout->pos.y = std::max(icon->pos.y + icon->pos.h - 4, prevGlyphPos.y + prevGlyphPos.h) + 4;
9374 glyphCallout->pos.x = prevGlyphPos.x + prevGlyphPos.w / 2 - glyphCallout->pos.w / 2;
9375 if ( glyphCallout->pos.x % 2 == 1 )
9376 {
9377 ++glyphCallout->pos.x;
9378 }
9379 }
9380 }
9381
9382 if ( !glyphCallout->disabled )
9383 {
9384 glyphCallout->color = makeColor(255, 255, 255, 255);
9385 textCallout->setText(Language::get(6049));
9386 textCallout->setColor(makeColor(255, 255, 255, 255));
9387 SDL_Rect textPos = text->getSize();
9388 textPos.x = glyphCallout->pos.x + glyphCallout->pos.w + 4;
9389 textPos.y = glyphCallout->pos.y + 2;
9390
9391 if ( auto imgGet = Image::get(glyphPathPressed.c_str()) )
9392 {
9393 if ( imgGet->getHeight() != glyphCallout->pos.h )
9394 {
9395 textPos.y += (glyphCallout->pos.h - imgGet->getHeight()) / 2;
9396 }
9397 }
9398 if ( glyphCallout->pos.h != nominalGlyphHeight )
9399 {
9400 textPos.y += (glyphCallout->pos.h - nominalGlyphHeight) / 2;
9401 }
9402 textPos.w = textCallout->getTextObject()->getWidth();
9403 textCallout->setSize(textPos);
9404 textCallout->setDisabled(false);
9405
9406 promptPos.w = std::max(textPos.x + textPos.w, promptPos.w);
9407 promptPos.h = std::max(textPos.y + textPos.h, promptPos.h);
9408 promptPos.w = std::max(glyphCallout->pos.x + glyphCallout->pos.w, promptPos.w);
9409 promptPos.h = std::max(glyphCallout->pos.y + glyphCallout->pos.h, promptPos.h);
9410
9411 prevGlyphPos = glyphCallout->pos;
9412 }
9413 }
9414 }
9415
9416 if ( player.worldUI.isEnabled() )
9417 {
9418 if ( !text->isDisabled() && !glyph->disabled && player.worldUI.tooltipsInRange.size() > 1 )
9419 {
9420 if ( Input::inputs[player.playernum].input("Interact Tooltip Next").type != Input::binding_t::bindtype_t::INVALID )
9421 {
9422 auto glyphPathUnpressed = Input::inputs[player.playernum].getGlyphPathForBinding("Interact Tooltip Next", false);
9423 auto glyphPathPressed = Input::inputs[player.playernum].getGlyphPathForBinding("Interact Tooltip Next", true);
9424
9425 if ( ticks % 50 < 25 )
9426 {
9427 glyphCycle->path = glyphPathPressed;
9428 if ( auto imgGet = Image::get(glyphCycle->path.c_str()) )
9429 {
9430 glyphCycle->disabled = false;
9431 glyphCycle->pos = SDL_Rect{ 0, 0, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
9432 glyphCycle->pos.y = prevGlyphPos.y + prevGlyphPos.h + 4;
9433 glyphCycle->pos.x = prevGlyphPos.x + prevGlyphPos.w / 2 - glyphCycle->pos.w / 2;
9434 if ( glyphCycle->pos.x % 2 == 1 )
9435 {
9436 ++glyphCycle->pos.x;
9437 }
9438 if ( auto imgGetUnpressed = Image::get(glyphPathUnpressed.c_str()) )
9439 {
9440 const int unpressedHeight = imgGetUnpressed->getHeight();
9441 if ( unpressedHeight != glyphCycle->pos.h )
9442 {
9443 glyphCycle->pos.y -= (glyphCycle->pos.h - unpressedHeight);
9444 }
9445
9446 /*if ( unpressedHeight != nominalGlyphHeight )
9447 {
9448 glyph->pos.y -= (unpressedHeight - nominalGlyphHeight) / 2;
9449 }*/
9450 }
9451 textPos.x += prevGlyphPos.w;
9452 }
9453 }
9454 else
9455 {
9456 glyphCycle->path = glyphPathUnpressed;
9457 if ( auto imgGet = Image::get(glyphCycle->path.c_str()) )
9458 {
9459 glyphCycle->disabled = false;
9460 glyphCycle->pos = SDL_Rect{ 0, 0, (int)imgGet->getWidth(), (int)imgGet->getHeight() };
9461 glyphCycle->pos.y = prevGlyphPos.y + prevGlyphPos.h + 4;
9462 glyphCycle->pos.x = prevGlyphPos.x + prevGlyphPos.w / 2 - glyphCycle->pos.w / 2;
9463 if ( glyphCycle->pos.x % 2 == 1 )
9464 {
9465 ++glyphCycle->pos.x;
9466 }
9467 }
9468 }
9469
9470 if ( !glyphCycle->disabled )
9471 {
9472 //glyphCycle->pos.x += 16;
9473 //interactPrompt.promptAnim = 1.0;
9474 glyphCycle->color = makeColor(255, 255, 255, 255 * interactPrompt.promptAnim);
9475 textCycle->setText(Language::get(4326));
9476 textCycle->setColor(makeColor(255, 255, 255, 255 * interactPrompt.promptAnim));
9477 SDL_Rect textPos = text->getSize();
9478 textPos.x = glyphCycle->pos.x + glyphCycle->pos.w + 4;
9479 textPos.y = glyphCycle->pos.y + 2;
9480
9481 if ( auto imgGet = Image::get(glyphPathPressed.c_str()) )
9482 {
9483 if ( imgGet->getHeight() != glyphCycle->pos.h )
9484 {
9485 textPos.y += (glyphCycle->pos.h - imgGet->getHeight()) / 2;
9486 }
9487 }
9488 if ( glyphCycle->pos.h != nominalGlyphHeight )
9489 {
9490 textPos.y += (glyphCycle->pos.h - nominalGlyphHeight) / 2;
9491 }
9492 textPos.w = textCycle->getTextObject()->getWidth();
9493 glyphCycle->pos.y += 4 * sin(interactPrompt.cycleAnim * PI3.14159265358979323846);
9494 textPos.y += 4 * sin(interactPrompt.cycleAnim * PI3.14159265358979323846);
9495 textCycle->setSize(textPos);
9496 textCycle->setDisabled(false);
9497
9498 promptPos.w = std::max(textPos.x + textPos.w, promptPos.w);
9499 promptPos.h = std::max(textPos.y + textPos.h, promptPos.h);
9500 promptPos.w = std::max(glyphCycle->pos.x + glyphCycle->pos.w, promptPos.w);
9501 promptPos.h = std::max(glyphCycle->pos.y + glyphCycle->pos.h, promptPos.h);
9502
9503 if ( interactPrompt.processedOnTick != ticks )
9504 {
9505 interactPrompt.processedOnTick = ticks;
9506 ++interactPrompt.activeTicks;
9507 }
9508
9509 if ( interactPrompt.activeTicks > TICKS_PER_SECOND50 / 10 )
9510 {
9511 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
9512 real_t setpointDiff = fpsScale * std::max(.1, (1.0 - interactPrompt.promptAnim)) / 2.5;
9513 interactPrompt.promptAnim += setpointDiff;
9514 interactPrompt.promptAnim = std::min(1.0, interactPrompt.promptAnim);
9515 }
9516 }
9517
9518 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
9519 real_t setpointDiff = fpsScale * 0.15;
9520 interactPrompt.cycleAnim += setpointDiff;
9521 interactPrompt.cycleAnim = std::min(1.0, interactPrompt.cycleAnim);
9522 }
9523 }
9524 }
9525 if ( textCycle->isDisabled() )
9526 {
9527 interactPrompt.promptAnim = 0.0;
9528 interactPrompt.activeTicks = 0;
9529 interactPrompt.cycleAnim = 1.0;
9530 }
9531 worldTooltipFrame->setSize(promptPos);
9532 worldTooltipFrame->setDisabled(false);
9533}
9534
9535std::string actionPromptBackingIconPath00 = "";
9536std::string actionPromptBackingIconPath20 = "";
9537std::string actionPromptBackingIconPath60 = "";
9538std::string actionPromptBackingIconPath100 = "";
9539
9540void createActionPrompts(const int player)
9541{
9542 auto& hud_t = players[player]->hud;
9543 auto& actionPromptFrame = hud_t.actionPromptsFrame;
9544 actionPromptFrame = hud_t.hudFrame->addFrame("action prompts");
9545 actionPromptFrame->setHollow(true);
9546 actionPromptFrame->setBorder(0);
9547 actionPromptFrame->setOwner(player);
9548 actionPromptFrame->setSize(SDL_Rect{ 0, 0, hud_t.hudFrame->getSize().w, hud_t.hudFrame->getSize().h });
9549 actionPromptFrame->setDisabled(true);
9550
9551 const int iconSize = Player::HUD_t::actionPromptIconSize;
9552 const int glyphSize = 32;
9553 const int iconBackingSize = Player::HUD_t::actionPromptBackingSize;
9554 const int maxWidth = std::max(iconSize, iconBackingSize);
9555 const int maxHeight = std::max(iconSize, iconBackingSize);
9556 const int promptHeight = maxHeight + glyphSize; // vertical space for prompts
9557
9558 SDL_Rect iconPos{ maxWidth / 2 - iconSize / 2 - 1,
9559 promptHeight - maxHeight / 2 - iconSize / 2 - 1,
9560 iconSize,
9561 iconSize };
9562 SDL_Rect iconBackingPos{ maxWidth / 2 - iconBackingSize / 2,
9563 promptHeight - maxHeight / 2 - iconBackingSize / 2,
9564 iconBackingSize,
9565 iconBackingSize };
9566
9567 const char* promptFont = "fonts/pixel_maz_multiline.ttf#16#2";
9568
9569 Uint32 iconColor = makeColor(255, 255, 255, Player::HUD_t::actionPromptIconOpacity);
9570 Uint32 iconBackingColor = makeColor(255, 255, 255, Player::HUD_t::actionPromptIconBackingOpacity);
9571
9572 auto mainHand = actionPromptFrame->addFrame("action mainhand");
9573 mainHand->setSize(SDL_Rect{ 400, 400, maxWidth, promptHeight });
9574 mainHand->addImage(iconBackingPos,
9575 iconBackingColor, actionPromptBackingIconPath00.c_str(), "action img backing");
9576 mainHand->addImage(iconPos,
9577 iconColor, "images/system/white.png", "action img");
9578 Frame::image_t* glyph = actionPromptFrame->addImage(SDL_Rect{ 0, 0, mainHand->getSize().w, glyphSize },
9579 0xFFFFFFFF, "images/system/white.png", "action mainhand glyph");
9580 glyph->ontop = true;
9581 auto mainHandText = actionPromptFrame->addField("action mainhand text", 64);
9582 mainHandText->setFont(promptFont);
9583 mainHandText->setText(Language::get(5963));
9584 mainHandText->setHJustify(Field::justify_t::CENTER);
9585
9586 auto offHand = actionPromptFrame->addFrame("action offhand");
9587 offHand->setSize(SDL_Rect{ 440, 400, maxWidth, promptHeight });
9588 offHand->addImage(iconBackingPos,
9589 iconBackingColor, actionPromptBackingIconPath00.c_str(), "action img backing");
9590 offHand->addImage(iconPos,
9591 iconColor, "images/system/white.png", "action img");
9592 glyph = actionPromptFrame->addImage(SDL_Rect{ 0, 0, mainHand->getSize().w, glyphSize },
9593 0xFFFFFFFF, "images/system/white.png", "action offhand glyph");
9594 glyph->ontop = true;
9595 auto offHandText = actionPromptFrame->addField("action offhand text", 64);
9596 offHandText->setFont(promptFont);
9597 offHandText->setText(Language::get(5964));
9598 offHandText->setHJustify(Field::justify_t::CENTER);
9599
9600 auto magic = actionPromptFrame->addFrame("action magic");
9601 magic->setSize(SDL_Rect{ 480, 400, maxWidth, promptHeight });
9602 magic->addImage(iconBackingPos,
9603 iconBackingColor, actionPromptBackingIconPath00.c_str(), "action img backing");
9604 magic->addImage(iconPos,
9605 iconColor, "images/system/white.png", "action img");
9606 glyph = actionPromptFrame->addImage(SDL_Rect{ 0, 0, mainHand->getSize().w, glyphSize },
9607 0xFFFFFFFF, "images/system/white.png", "action magic glyph");
9608 glyph->ontop = true;
9609 auto magicText = actionPromptFrame->addField("action magic text", 64);
9610 magicText->setFont(promptFont);
9611 magicText->setText(Language::get(5965));
9612 magicText->setHJustify(Field::justify_t::CENTER);
9613
9614 auto sneak = actionPromptFrame->addFrame("action sneak");
9615 sneak->setSize(SDL_Rect{ 480, 400, maxWidth, promptHeight });
9616 sneak->addImage(iconBackingPos,
9617 iconBackingColor, actionPromptBackingIconPath00.c_str(), "action img backing");
9618 sneak->addImage(iconPos,
9619 iconColor, "images/system/white.png", "action img");
9620 glyph = actionPromptFrame->addImage(SDL_Rect{ 0, 0, mainHand->getSize().w, glyphSize },
9621 0xFFFFFFFF, "images/system/white.png", "action sneak glyph");
9622 glyph->ontop = true;
9623 auto sneakText = actionPromptFrame->addField("action sneak text", 64);
9624 sneakText->setFont(promptFont);
9625 sneakText->setText(Language::get(5964));
9626 sneakText->setHJustify(Field::justify_t::CENTER);
9627}
9628
9629void drawActionPromptCooldownCallback(const Widget& widget, SDL_Rect rect)
9630{
9631 const int player = widget.getOwner();
9632
9633 const Frame* parent = static_cast<const Frame*>(widget.getParent());
9634 {
9635 SDL_Rect drawRect = rect;
9636 drawRect.x += 22;
9637 drawRect.y += 22;
9638 drawRect.w = 44;
9639 drawRect.h = 44;
9640 real_t opacity = 192;
9641 if ( parent && parent->getOpacity() < 100.0 )
9642 {
9643 opacity *= parent->getOpacity() / 100.0;
9644 }
9645 const auto& ghost = players[player]->ghost;
9646 int prompt = reinterpret_cast<intptr_t>(widget.getUserData()) - 1;
9647 real_t cooldownProgress = 0.0;
9648 bool errorFlash = false;
9649 int actionPoints = 0;
9650 switch ( static_cast<Player::HUD_t::ActionPrompts>(prompt) )
9651 {
9652 case Player::HUD_t::ACTION_PROMPT_MAGIC:
9653 cooldownProgress = (ghost.cooldownTeleport / (real_t)ghost.cooldownTeleportDelay);
9654 if ( ghost.errorFlashTeleportTicks > 0 && (Player::Ghost_t::errorFlashTicks - ghost.errorFlashTeleportTicks) % 20 < 10 )
9655 {
9656 errorFlash = true;
9657 }
9658 break;
9659 case Player::HUD_t::ACTION_PROMPT_OFFHAND:
9660 if ( ghost.pushPoints > 0 )
9661 {
9662 cooldownProgress = 0.0;
9663 }
9664 else
9665 {
9666 cooldownProgress = (ghost.cooldownPush / (real_t)ghost.cooldownPushDelay);
9667 }
9668 actionPoints = ghost.pushPoints;
9669 if ( ghost.errorFlashPushTicks > 0 && (Player::Ghost_t::errorFlashTicks - ghost.errorFlashPushTicks) % 20 < 10 )
9670 {
9671 errorFlash = true;
9672 }
9673 break;
9674 case Player::HUD_t::ACTION_PROMPT_MAINHAND:
9675 cooldownProgress = (ghost.cooldownChill / (real_t)ghost.cooldownChillDelay);
9676 if ( ghost.errorFlashChillTicks > 0 && (Player::Ghost_t::errorFlashTicks - ghost.errorFlashChillTicks) % 20 < 10 )
9677 {
9678 errorFlash = true;
9679 }
9680 break;
9681 case Player::HUD_t::ACTION_PROMPT_SNEAK:
9682 break;
9683 default:
9684 break;
9685 }
9686 if ( cooldownProgress > 0.0 )
9687 {
9688 if ( errorFlash )
9689 {
9690 drawClockwiseSquareMesh("images/ui/HUD/HUD_ActionPromptBacking00_02_Cooldown.png",
9691 cooldownProgress,
9692 drawRect, makeColor(255, 255, 255, opacity));
9693 }
9694 else
9695 {
9696 Uint8 r, g, b, a;
9697 getColor(hudColors.characterSheetRed, &r, &g, &b, &a);
9698 drawClockwiseSquareMesh("images/ui/HUD/HUD_ActionPromptBacking00_02_Cooldown.png",
9699 cooldownProgress,
9700 drawRect, makeColor(r, g, b, opacity));
9701 }
9702 }
9703 /*if ( actionPoints > 0 )
9704 {
9705 std::string str = std::to_string(actionPoints);
9706 if ( auto textGet = Text::get(str.c_str(), smallfont_outline, makeColorRGB(255, 255, 255), 0) )
9707 {
9708 textGet->drawColor(SDL_Rect{ 0,0,0,0 }, SDL_Rect{ drawRect.x - 6, drawRect.y + 1, 0, 0 },
9709 SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY },
9710 makeColor(255, 255, 255, 255));
9711 }
9712 }*/
9713 }
9714}
9715
9716int Player::HUD_t::actionPromptOffsetX = 280;
9717int Player::HUD_t::actionPromptOffsetXGhostPrompts = 140;
9718int Player::HUD_t::actionPromptOffsetY = 22;
9719int Player::HUD_t::actionPromptBackingSize = 44;
9720int Player::HUD_t::actionPromptIconSize = 32;
9721int Player::HUD_t::actionPromptIconOpacity = 255;
9722int Player::HUD_t::actionPromptIconBackingOpacity = 255;
9723static ConsoleVariable<bool> disableActionPrompts(
9724 "/disableactionprompts", false, "Disable action prompts in HUD");
9725void Player::HUD_t::updateActionPrompts()
9726{
9727 if ( !hudFrame )
9728 {
9729 return;
9730 }
9731
9732 if ( !actionPromptsFrame )
9733 {
9734 createActionPrompts(player.playernum);
9735 if ( !actionPromptsFrame )
9736 {
9737 return;
9738 }
9739 }
9740
9741
9742 int playercount = 0;
9743 for (int c = 0; c < MAXPLAYERS4; ++c) {
9744 if (!client_disconnected[c] && players[c]->isLocalPlayer()) {
9745 ++playercount;
9746 }
9747 }
9748
9749 bool ghostPrompts = player.ghost.isActive();
9750 static ConsoleVariable<int> actionPromptCompactHeightY("/actionpromptcompactheighty", 16);
9751 bShowActionPrompts = *disableActionPrompts;
9752 bShortHPMPForActionBars = false;
9753
9754 if ( playercount == 2 && !*MainMenu::vertical_splitscreen && !ghostPrompts
9755 && !*disableActionPrompts && *MainMenu::clipped_splitscreen )
9756 {
9757 bShortHPMPForActionBars = true;
9758 }
9759
9760 if ( playercount > 2
9761 || (playercount == 2
9762 && (*MainMenu::vertical_splitscreen
9763 /*|| !player.shootmode */
9764 || (!player.shootmode && player.hud.compactLayoutMode == Player::HUD_t::COMPACT_LAYOUT_CHARSHEET)
9765 || (!player.hotbar.useHotbarFaceMenu && *MainMenu::clipped_splitscreen)))
9766 || *disableActionPrompts )
9767 {
9768 if ( !ghostPrompts )
9769 {
9770 actionPromptsFrame->setDisabled(true);
9771 bShowActionPrompts = false;
9772 return;
9773 }
9774 }
9775 actionPromptsFrame->setDisabled(false);
9776 actionPromptsFrame->setSize(SDL_Rect{ 0, 0, hudFrame->getSize().w, hudFrame->getSize().h });
9777
9778 bool tempHideActionPrompts = false;
9779 bool fadeOut = false;
9780 if ( ghostPrompts )
9781 {
9782 if ( player.bUseCompactGUIHeight() && CalloutMenu[player.playernum].calloutMenuIsOpen() && !CalloutMenu[player.playernum].selectMoveTo )
9783 {
9784 tempHideActionPrompts = true;
9785 }
9786 }
9787
9788 if ( tempHideActionPrompts )
9789 {
9790 if ( fadeOut )
9791 {
9792 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
9793 real_t setpointDiff = fpsScale * std::max(.1, (1.0 - animHideActionPrompts)) / 2.5;
9794 animHideActionPrompts += setpointDiff;
9795 animHideActionPrompts = std::min(1.0, animHideActionPrompts);
9796 }
9797 else
9798 {
9799 animHideActionPrompts = 1.0;
9800 }
9801 }
9802 else
9803 {
9804 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
9805 real_t setpointDiff = fpsScale * std::max(.1, (animHideActionPrompts)) / 2.5;
9806 animHideActionPrompts -= setpointDiff;
9807 animHideActionPrompts = std::max(0.0, animHideActionPrompts);
9808 }
9809
9810 actionPromptsFrame->setInheritParentFrameOpacity(false);
9811 real_t opacity = 1.0;
9812 if ( auto parent = actionPromptsFrame->getParent() )
9813 {
9814 opacity *= actionPromptsFrame->getParent()->getOpacity() / 100.0;
9815 }
9816 actionPromptsFrame->setOpacity((opacity - animHideActionPrompts) * 100.0);
9817
9818 const int iconSize = Player::HUD_t::actionPromptIconSize;
9819 const int glyphSize = 32;
9820 const int iconBackingSize = Player::HUD_t::actionPromptBackingSize;
9821 const int maxWidth = std::max(iconSize, iconBackingSize);
9822 const int maxHeight = std::max(iconSize, iconBackingSize);
9823 const int promptHeight = maxHeight + glyphSize; // vertical space for prompts
9824
9825 SDL_Rect iconPos{ maxWidth / 2 - iconSize / 2 - 0,
9826 maxHeight / 2 - iconSize / 2 - 0,
9827 iconSize,
9828 iconSize };
9829 SDL_Rect iconBackingPos{ maxWidth / 2 - iconBackingSize / 2,
9830 maxHeight / 2 - iconBackingSize / 2,
9831 iconBackingSize,
9832 iconBackingSize };
9833
9834 struct PromptInfo {
9835 std::string name;
9836 ActionPrompts promptType;
9837 std::string inputName;
9838 };
9839
9840 std::vector<PromptInfo> allPrompts;
9841 const std::string blockBinding = Input::inputs[player.playernum].binding("Defend");
9842 const std::string sneakBinding = Input::inputs[player.playernum].binding("Sneak");
9843 bool sneakingSeparateFromBlock = false;// blockBinding != sneakBinding;
9844 if ( ghostPrompts )
9845 {
9846 sneakingSeparateFromBlock = true;
9847 allPrompts.emplace_back(PromptInfo{ "action sneak", ACTION_PROMPT_SNEAK, "Sneak" });
9848 allPrompts.emplace_back(PromptInfo{ "action offhand", ACTION_PROMPT_OFFHAND, "Use" });
9849 allPrompts.emplace_back(PromptInfo{ "action magic", ACTION_PROMPT_MAGIC, "Cast Spell" });
9850 allPrompts.emplace_back(PromptInfo{ "action mainhand", ACTION_PROMPT_MAINHAND, "Attack" });
9851 }
9852 else if ( sneakingSeparateFromBlock )
9853 {
9854 allPrompts.emplace_back(PromptInfo{ "action offhand", ACTION_PROMPT_OFFHAND, "Defend" });
9855 allPrompts.emplace_back(PromptInfo{ "action sneak", ACTION_PROMPT_SNEAK, "Sneak" });
9856 allPrompts.emplace_back(PromptInfo{ "action magic", ACTION_PROMPT_MAGIC, "Cast Spell" });
9857 allPrompts.emplace_back(PromptInfo{ "action mainhand", ACTION_PROMPT_MAINHAND, "Attack" });
9858 }
9859 else
9860 {
9861 allPrompts.emplace_back(PromptInfo{ "action offhand", ACTION_PROMPT_OFFHAND, "Defend" });
9862 allPrompts.emplace_back(PromptInfo{ "action sneak", ACTION_PROMPT_SNEAK, "Sneak" });
9863 allPrompts.emplace_back(PromptInfo{ "action magic", ACTION_PROMPT_MAGIC, "Cast Spell" });
9864 allPrompts.emplace_back(PromptInfo{ "action mainhand", ACTION_PROMPT_MAINHAND, "Attack" });
9865 }
9866
9867 Uint32 iconBackingColor = makeColor(255, 255, 255, Player::HUD_t::actionPromptIconBackingOpacity);
9868 Uint32 iconColor = makeColor(255, 255, 255, Player::HUD_t::actionPromptIconOpacity);
9869 Uint32 iconFadedColor = makeColor(255, 255, 255, Player::HUD_t::actionPromptIconOpacity * .5);
9870 Uint32 iconFadedBackingColor = makeColor(255, 255, 255, Player::HUD_t::actionPromptIconBackingOpacity * .5);
9871
9872 int index = 0;
9873 for ( auto& promptInfo : allPrompts )
9874 {
9875 if ( auto prompt = actionPromptsFrame->findFrame(promptInfo.name.c_str()) )
9876 {
9877 prompt->setDisabled(false);
9878
9879 std::string glyphName = promptInfo.name + " glyph";
9880 auto img = prompt->findImage("action img");
9881 auto glyph = actionPromptsFrame->findImage(glyphName.c_str());
9882 auto imgBacking = prompt->findImage("action img backing");
9883
9884 SDL_Rect promptPos = prompt->getSize();
9885 promptPos.w = maxWidth;
9886 promptPos.h = promptHeight;
9887 int actionPromptOffsetXTotal = actionPromptOffsetX;
9888 prompt->setDrawCallback(nullptr);
9889 prompt->setUserData(nullptr);
9890 if ( ghostPrompts )
9891 {
9892 actionPromptOffsetXTotal = actionPromptOffsetXGhostPrompts;
9893 prompt->setUserData((void*)(intptr_t)(promptInfo.promptType + 1));
9894 prompt->setDrawCallback([](const Widget& widget, SDL_Rect rect) {
9895 drawActionPromptCooldownCallback(widget, rect);
9896 });
9897 }
9898 else if ( !player.hotbar.useHotbarFaceMenu )
9899 {
9900 actionPromptOffsetXTotal += 22;
9901 }
9902 switch ( index )
9903 {
9904 case 0: // to the left of hotbar
9905 promptPos.x = hudFrame->getSize().w / 2 - actionPromptOffsetXTotal;
9906 break;
9907 case 1: // to the left after the previous
9908 promptPos.x = hudFrame->getSize().w / 2 - actionPromptOffsetXTotal;
9909 promptPos.x -= 16;
9910 promptPos.x -= promptPos.w;
9911 break;
9912 case 2: // to the right of hotbar
9913 promptPos.x = hudFrame->getSize().w / 2 + actionPromptOffsetXTotal;
9914 promptPos.x -= promptPos.w;
9915 break;
9916 case 3: // to the right after the previous
9917 promptPos.x = hudFrame->getSize().w / 2 + actionPromptOffsetXTotal;
9918 promptPos.x += 16;
9919 break;
9920 default:
9921 break;
9922 }
9923 promptPos.y = hudFrame->getSize().h + player.hotbar.getHotbarStartY2() - promptPos.h;
9924 promptPos.y += maxHeight / 2;
9925 promptPos.y += actionPromptOffsetY;
9926 if ( player.bUseCompactGUIHeight() )
9927 {
9928 promptPos.y += *actionPromptCompactHeightY;
9929 }
9930 prompt->setSize(promptPos);
9931
9932 img->pos = iconPos;
9933 imgBacking->pos = iconBackingPos;
9934 img->color = iconColor;
9935 imgBacking->color = iconBackingColor;
9936
9937 Field* promptText = nullptr;
9938 switch ( promptInfo.promptType )
9939 {
9940 case ACTION_PROMPT_MAINHAND:
9941 promptText = actionPromptsFrame->findField("action mainhand text");
9942 break;
9943 case ACTION_PROMPT_OFFHAND:
9944 promptText = actionPromptsFrame->findField("action offhand text");
9945 break;
9946 case ACTION_PROMPT_MAGIC:
9947 promptText = actionPromptsFrame->findField("action magic text");
9948 break;
9949 case ACTION_PROMPT_SNEAK:
9950 promptText = actionPromptsFrame->findField("action sneak text");
9951 break;
9952 default:
9953 break;
9954 }
9955
9956 if ( promptInfo.promptType == ACTION_PROMPT_SNEAK && !sneakingSeparateFromBlock )
9957 {
9958 prompt->setDisabled(true);
9959 promptText->setDisabled(true);
9960 glyph->disabled = true;
9961 ++index;
9962 continue;
9963 }
9964
9965 std::string textForPrompt = "";
9966 int skillForPrompt = getActionIconForPlayer(promptInfo.promptType, textForPrompt);
9967 if ( promptInfo.promptType == ACTION_PROMPT_OFFHAND && sneakingSeparateFromBlock && !ghostPrompts
9968 && skillForPrompt == PRO_STEALTH )
9969 {
9970 // offhand wants to display sneak - redundant, hide this prompt
9971 prompt->setDisabled(true);
9972 promptText->setDisabled(true);
9973 glyph->disabled = true;
9974 ++index;
9975 continue;
9976 }
9977
9978 if ( player.shootmode || player.gui_mode == GUI_MODE_FOLLOWERMENU
9979 || player.gui_mode == GUI_MODE_SIGN
9980 || player.gui_mode == GUI_MODE_CALLOUT )
9981 {
9982 promptText->setDisabled(true);
9983 }
9984 else
9985 {
9986 promptText->setDisabled(false);
9987 promptText->setText(textForPrompt.c_str());
9988 SDL_Rect textPos;
9989 auto textGetLongestLine = Text::get(promptText->getLongestLine().c_str(),
9990 promptText->getFont(),
9991 promptText->getTextColor(),
9992 promptText->getOutlineColor());
9993 textPos.w = textGetLongestLine->getWidth();
9994 textPos.h = promptText->getNumTextLines() * Font::get(promptText->getFont())->height();
9995 textPos.x = promptPos.x + promptPos.w / 2 - textPos.w / 2;
9996 //textPos.y = promptPos.y + imgBacking->pos.y - textPos.h - 4; -- top aligned
9997 textPos.y = promptPos.y + imgBacking->pos.y + imgBacking->pos.h + 1;
9998 promptText->setSize(textPos);
9999 }
10000
10001 std::string skillImg = "";
10002 if ( skillForPrompt >= 0 && skillForPrompt < NUMPROFICIENCIES )
10003 {
10004 for ( auto& skill : player.skillSheet.skillSheetData.skillEntries )
10005 {
10006 if ( skill.skillId == skillForPrompt )
10007 {
10008 if ( skillCapstoneUnlocked(player.playernum, skillForPrompt) )
10009 {
10010 skillImg = skill.skillIconPathLegend32px;
10011 }
10012 else
10013 {
10014 skillImg = skill.skillIconPath32px;
10015 }
10016 break;
10017 }
10018 }
10019 if ( stats[player.playernum]->getModifiedProficiency(skillForPrompt) >= SKILL_LEVEL_LEGENDARY )
10020 {
10021 imgBacking->path = actionPromptBackingIconPath100;
10022 }
10023 else if ( stats[player.playernum]->getModifiedProficiency(skillForPrompt) >= SKILL_LEVEL_EXPERT )
10024 {
10025 imgBacking->path = actionPromptBackingIconPath60;
10026 }
10027 else if ( stats[player.playernum]->getModifiedProficiency(skillForPrompt) >= SKILL_LEVEL_BASIC )
10028 {
10029 imgBacking->path = actionPromptBackingIconPath20;
10030 }
10031 else
10032 {
10033 imgBacking->path = actionPromptBackingIconPath00;
10034 }
10035 }
10036 else if ( ghostPrompts )
10037 {
10038 imgBacking->path = actionPromptBackingIconPath00;
10039 switch ( promptInfo.promptType )
10040 {
10041 case ACTION_PROMPT_MAGIC:
10042 skillImg = "*images/ui/HUD/HUD_Ghost_Haunt.png";
10043 img->pos.x -= 2;
10044 img->pos.y -= 2;
10045 img->pos.w = 36;
10046 img->pos.h = 36;
10047 break;
10048 case ACTION_PROMPT_MAINHAND:
10049 skillImg = "*images/ui/HUD/HUD_Ghost_Chill.png";
10050 img->pos.x -= 2;
10051 img->pos.y -= 2;
10052 img->pos.w = 36;
10053 img->pos.h = 36;
10054 break;
10055 case ACTION_PROMPT_OFFHAND:
10056 skillImg = "*images/ui/HUD/HUD_Ghost_Push.png";
10057 img->pos.x -= 2;
10058 img->pos.y -= 2;
10059 img->pos.w = 36;
10060 img->pos.h = 36;
10061 break;
10062 case ACTION_PROMPT_SNEAK:
10063 for ( auto& skill : player.skillSheet.skillSheetData.skillEntries )
10064 {
10065 if ( skill.skillId == PRO_STEALTH )
10066 {
10067 skillImg = skill.skillIconPath32px;
10068 break;
10069 }
10070 }
10071 break;
10072 default:
10073 break;
10074 }
10075 }
10076 if ( skillImg == "" )
10077 {
10078 prompt->setDisabled(true);
10079 }
10080 else
10081 {
10082 prompt->setDisabled(false);
10083 img->path = skillImg;
10084 if ( img->path[0] != '*' )
10085 {
10086 img->path = '*' + img->path;
10087 }
10088 }
10089
10090 bool pressed = false;
10091 /*if ( skillForPrompt == PRO_SHIELD && stats[player.playernum]->defending )
10092 {
10093 pressed = true;
10094 }
10095 if ( skillForPrompt == PRO_STEALTH && !stats[player.playernum]->defending
10096 && stats[player.playernum]->sneaking )
10097 {
10098 pressed = true;
10099 }*/
10100
10101 std::string bindingName = promptInfo.inputName.c_str();
10102 if ( !ghostPrompts )
10103 {
10104 if ( skillForPrompt == PRO_STEALTH && promptInfo.promptType == ACTION_PROMPT_OFFHAND )
10105 {
10106 bindingName = "Sneak";
10107 }
10108 }
10109 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding(bindingName.c_str(), pressed);
10110 glyph->disabled = prompt->isDisabled();
10111 if ( !player.shootmode || player.gui_mode == GUI_MODE_FOLLOWERMENU
10112 || player.gui_mode == GUI_MODE_SIGN
10113 || player.gui_mode == GUI_MODE_CALLOUT )
10114 {
10115 glyph->disabled = true;
10116 }
10117 else if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
10118 {
10119 glyph->disabled = false;
10120 }
10121 else
10122 {
10123 glyph->disabled = false;
10124 }
10125 if ( auto imgGet = Image::get(glyph->path.c_str()) )
10126 {
10127 glyph->pos.w = (int)imgGet->getWidth();
10128 glyph->pos.h = (int)imgGet->getHeight();
10129 }
10130 glyph->pos.x = prompt->getSize().x + prompt->getSize().w / 2 - glyph->pos.w / 2; // center the x for the glyph
10131 int glyphToImgPadY = 0;
10132 if ( Input::inputs[player.playernum].input(bindingName.c_str()).type == Input::binding_t::bindtype_t::MOUSE_BUTTON )
10133 {
10134 glyphToImgPadY += 4;
10135 }
10136 int pressedOffset = 0;
10137 /*if ( pressed )
10138 {
10139 if ( auto imgGet = Image::get(Input::inputs[player.playernum].getGlyphPathForBinding(promptInfo.inputName.c_str()).c_str()) )
10140 {
10141 int unpressedHeight = (int)imgGet->getHeight();
10142 if ( glyph->pos.h != unpressedHeight )
10143 {
10144 pressedOffset = (unpressedHeight - glyph->pos.h);
10145 }
10146 }
10147 }*/
10148 glyph->pos.y = prompt->getSize().y + img->pos.y + img->pos.h - glyphToImgPadY + pressedOffset; // just above the skill img with some padding
10149
10150 if ( !ghostPrompts )
10151 {
10152 if ( promptInfo.promptType == ACTION_PROMPT_SNEAK )
10153 {
10154 if ( !(!stats[player.playernum]->defending && stats[player.playernum]->sneaking) )
10155 {
10156 //glyph->disabled = true;
10157 img->color = iconFadedColor;
10158 //imgBacking->color = iconFadedBackingColor;
10159 }
10160 }
10161 if ( promptInfo.promptType == ACTION_PROMPT_OFFHAND && skillForPrompt == PRO_SHIELD
10162 && sneakingSeparateFromBlock )
10163 {
10164 if ( !stats[player.playernum]->defending )
10165 {
10166 //glyph->disabled = true;
10167 img->color = iconFadedColor;
10168 //imgBacking->color = iconFadedBackingColor;
10169 }
10170 }
10171 }
10172 }
10173 ++index;
10174 }
10175}
10176
10177static Frame* createMinimap(int player);
10178
10179void createGameTimerFrame(const int player)
10180{
10181 auto& hud_t = players[player]->hud;
10182 hud_t.gameTimerFrame = hud_t.hudFrame->addFrame("timer");
10183 hud_t.gameTimerFrame->setHollow(true);
10184 hud_t.gameTimerFrame->setDisabled(true);
10185 hud_t.gameTimerFrame->setSize(SDL_Rect{ 0, 0, 142, 24 });
10186
10187 auto txt = hud_t.gameTimerFrame->addField("timer txt", 64);
10188 txt->setText("00:00:00");
10189 txt->setSize(SDL_Rect{ 0, 0, hud_t.gameTimerFrame->getSize().w, hud_t.gameTimerFrame->getSize().h });
10190 txt->setFont("fonts/pixel_maz_multiline.ttf#16#2");
10191 txt->setVJustify(Field::justify_t::TOP);
10192 txt->setHJustify(Field::justify_t::LEFT);
10193 txt->setColor(makeColor(255, 255, 255, 255));
10194
10195 auto seed = hud_t.gameTimerFrame->addField("seed txt", 64);
10196 seed->setText("");
10197 seed->setSize(SDL_Rect{ 0, 0, hud_t.gameTimerFrame->getSize().w, hud_t.gameTimerFrame->getSize().h });
10198 seed->setFont("fonts/pixel_maz_multiline.ttf#16#2");
10199 seed->setVJustify(Field::justify_t::TOP);
10200 seed->setHJustify(Field::justify_t::RIGHT);
10201 seed->setColor(makeColor(255, 255, 255, 255));
10202 seed->setDisabled(true);
10203}
10204
10205void createMapPromptFrame(const int player)
10206{
10207 auto& hud_t = players[player]->hud;
10208 hud_t.mapPromptFrame = hud_t.hudFrame->addFrame("map prompts");
10209 hud_t.mapPromptFrame->setHollow(true);
10210 hud_t.mapPromptFrame->setDisabled(true);
10211 hud_t.mapPromptFrame->setSize(SDL_Rect{ 0, 0, 142, 24 });
10212
10213 auto promptBg = hud_t.mapPromptFrame->addImage(SDL_Rect{ 0, 0, 280, 40 },
10214 0xFFFFFFFF, "*#images/ui/MapAndLog/HUD_MapPromptBase_00.png", "prompt bg");
10215
10216 auto imgPromptFrame = hud_t.mapPromptFrame->addFrame("img prompt frame");
10217 imgPromptFrame->setHollow(true);
10218
10219 auto scaleImg = imgPromptFrame->addImage(SDL_Rect{ 0, 0, 24, 24 },
10220 0xFFFFFFFF, "*#images/ui/MapAndLog/HUD_Map_Zoom_00.png", "scale img");
10221 auto scalePrompt = imgPromptFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
10222 0xFFFFFFFF, "*#images/system/white.png", "scale prompt");
10223
10224 auto expandImg = imgPromptFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
10225 0xFFFFFFFF, "*#images/ui/MapAndLog/HUD_Map_Scale_00.png", "expand img");
10226 auto expandPrompt = imgPromptFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
10227 0xFFFFFFFF, "*#images/system/white.png", "expand prompt");
10228}
10229
10230static void checkControllerState(int player) {
10231 auto& controllerFrame = players[player]->hud.controllerFrame;
10232 assert(controllerFrame)(static_cast<void> (0));
10233 if (multiplayer != SINGLE0) {
10234 player = 0;
10235 }
10236 if (inputs.getPlayerIDAllowedKeyboard() != player) {
10237 auto controller = inputs.getController(player);
10238 if (!controller || (controller && !controller->isActive())) {
10239 if (controllerFrame->isHollow()) {
10240 controllerFrame->setHollow(false);
10241 controllerFrame->setDisabled(false);
10242 MainMenu::controllerDisconnected(player);
10243 }
10244 return;
10245 }
10246 }
10247 controllerFrame->setDisabled(true);
10248 controllerFrame->setHollow(true);
10249}
10250
10251void HUDDrawGameEndHint(const int player, SDL_Rect rect)
10252{
10253 if ( multiplayer == CLIENT2 || multiplayer == SERVER1 )
10254 {
10255 bool everyonedead = true;
10256 for ( int i = 0; i < MAXPLAYERS4; ++i )
10257 {
10258 if ( players[i] )
10259 {
10260 if ( multiplayer == SERVER1 && (!client_disconnected[i] && players[i]->entity) )
10261 {
10262 everyonedead = false;
10263 }
10264 else if ( multiplayer == CLIENT2 && players[i]->entity )
10265 {
10266 everyonedead = false;
10267 }
10268 }
10269 }
10270
10271 if ( players[player]->bControlEnabled )
10272 {
10273 players[player]->hud.animDeadPromptDisplay = true;
10274 }
10275
10276 if ( everyonedead && players[player]->hud.animDeadPromptDisplay )
10277 {
10278 static ConsoleVariable<float> cvar_anim_dead_prompt_speed("/anim_dead_prompt_speed", 0.003);
10279 players[player]->hud.animDeadPrompt += *cvar_anim_dead_prompt_speed;
10280 if ( players[player]->hud.animDeadPrompt >= 1.0 )
10281 {
10282 players[player]->hud.animDeadPrompt = 0.0;
10283 }
10284 rect.x += rect.w / 2;
10285 rect.y += 8 - 1;
10286 if ( players[player]->hud.xpFrame && !players[player]->hud.xpFrame->isDisabled() )
10287 {
10288 rect.y += players[player]->hud.xpFrame->getSize().y;
10289 rect.y += players[player]->hud.xpFrame->getSize().h;
10290 }
10291 if ( auto textGet = Text::get(Language::get(6052), smallfont_outline, makeColorRGB(255, 255, 255), 0) )
10292 {
10293 Uint8 r, g, b, a;
10294 getColor(hudColors.characterSheetRed, &r, &g, &b, nullptr);
10295 real_t opacity = 0.5 + .4 * (1.0 * cos(players[player]->hud.animDeadPrompt * 2 * PI3.14159265358979323846) + 1.0);
10296 Uint32 color = makeColor(r, g, b, std::max(0.25, std::min(opacity, 1.0)) * 255);
10297 rect.x -= textGet->getWidth() / 2;
10298 textGet->drawColor(SDL_Rect{ 0, 0, 0, 0 }, SDL_Rect{ rect.x, rect.y, 0, 0 },
10299 SDL_Rect{ 0, 0, Frame::virtualScreenX, Frame::virtualScreenY },
10300 color);
10301 }
10302 }
10303 else
10304 {
10305 players[player]->hud.animDeadPromptDisplay = false;
10306 players[player]->hud.animDeadPrompt = 0.0;
10307 }
10308 }
10309 else
10310 {
10311 players[player]->hud.animDeadPromptDisplay = false;
10312 players[player]->hud.animDeadPrompt = 0.0;
10313 }
10314}
10315
10316void Player::HUD_t::processHUD()
10317{
10318 const SDL_Rect hudSize{
10319 players[player.playernum]->camera_virtualx1(),
10320 players[player.playernum]->camera_virtualy1(),
10321 players[player.playernum]->camera_virtualWidth(),
10322 players[player.playernum]->camera_virtualHeight()};
10323
10324 if ( !hudFrame )
10325 {
10326 char name[32];
10327 snprintf(name, sizeof(name), "player hud %d", player.playernum);
10328 hudFrame = gameUIFrame[player.playernum]->addFrame(name);
10329 hudFrame->setHollow(true);
10330 hudFrame->setBorder(0);
10331 hudFrame->setOwner(player.playernum);
10332 hudFrame->setDrawCallback([](const Widget& widget, SDL_Rect rect) {
10333 HUDDrawGameEndHint(widget.getOwner(), rect);
10334 });
10335 }
10336
10337 if ( !minotaurSharedDisplay && player.playernum == 0 )
10338 {
10339 minotaurSharedDisplay = gameUIFrame[player.playernum]->addFrame("minotaur shared display");
10340 minotaurSharedDisplay->setHollow(true);
10341 minotaurSharedDisplay->setOwner(player.playernum);
10342 minotaurSharedDisplay->setDisabled(true);
10343 minotaurSharedDisplay->setInheritParentFrameOpacity(false);
10344 auto img = minotaurSharedDisplay->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "*images/ui/HUD/HUD_Minotaur_00.png",
10345 "mino img");
10346 if ( auto imgGet = Image::get(img->path.c_str()) )
10347 {
10348 img->pos.w = imgGet->getWidth();
10349 img->pos.h = imgGet->getHeight();
10350 }
10351 minotaurSharedDisplay->setSize(SDL_Rect{ 0, 0, img->pos.w, img->pos.h });
10352 }
10353
10354 if ( !minotaurDisplay )
10355 {
10356 minotaurDisplay = hudFrame->addFrame("minotaur display");
10357 minotaurDisplay->setHollow(true);
10358 minotaurDisplay->setOwner(player.playernum);
10359 minotaurDisplay->setDisabled(true);
10360 minotaurDisplay->setInheritParentFrameOpacity(false);
10361 auto img = minotaurDisplay->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "*images/ui/HUD/HUD_Minotaur_00.png",
10362 "mino img");
10363 if ( auto imgGet = Image::get(img->path.c_str()) )
10364 {
10365 img->pos.w = imgGet->getWidth();
10366 img->pos.h = imgGet->getHeight();
10367 }
10368 minotaurDisplay->setSize(SDL_Rect{ 0, 0, img->pos.w, img->pos.h });
10369 }
10370
10371 if ( !controllerFrame )
10372 {
10373 controllerFrame = gameUIFrame[player.playernum]->addFrame("reconnect_controller");
10374 controllerFrame->setColor(0);
10375 controllerFrame->setOwner(player.playernum);
10376 controllerFrame->setBorder(0);
10377 controllerFrame->setDisabled(true);
10378 controllerFrame->setHollow(true);
10379 controllerFrame->setTickCallback([](Widget& widget){
10380 const int player = widget.getOwner();
10381 const bool disabled = ticks % TICKS_PER_SECOND50 >= TICKS_PER_SECOND50 / 2;
10382 const SDL_Rect hudSize{
10383 players[player]->camera_virtualx1(),
10384 players[player]->camera_virtualy1(),
10385 players[player]->camera_virtualWidth(),
10386 players[player]->camera_virtualHeight()};
10387 auto frame = static_cast<Frame*>(&widget); assert(frame)(static_cast<void> (0));
10388 auto image = frame->findImage("controller"); assert(image)(static_cast<void> (0));
10389 image->pos.x = (hudSize.w - image->pos.w) / 2;
10390 image->pos.y = (hudSize.h - image->pos.h) / 2;
10391 image->disabled = disabled;
10392 auto field = frame->findField("label"); assert(field)(static_cast<void> (0));
10393 field->setSize(image->pos);
10394 field->setInvisible(disabled);
10395 });
10396
10397 auto dimmer = controllerFrame->addImage(
10398 SDL_Rect{0, 0, hudSize.w, hudSize.h},
10399 makeColor(0, 0, 0, 191),
10400 "#*images/system/white.png",
10401 "dimmer");
10402
10403 const char* path = Input::getControllerGlyph(player.playernum);
10404 auto image = Image::get(path);
10405 const int w = image->getWidth();
10406 const int h = image->getHeight();
10407 const int x = (hudSize.w - w) / 2;
10408 const int y = (hudSize.h - h) / 2;
10409 auto controller = controllerFrame->addImage(
10410 SDL_Rect{x, y, w, h},
10411 0xffffffff, path,
10412 "controller");
10413
10414 char fmt[16];
10415 snprintf(fmt, sizeof(fmt), Language::get(5477), player.playernum + 1);
10416
10417 auto field = controllerFrame->addField("label", 16);
10418 field->setSize(SDL_Rect{x, y, w, h});
10419 field->setJustify(Field::justify_t::CENTER);
10420 field->setText(fmt);
10421 field->setFont(bigfont_outline);
10422 field->setColor(playerColor(player.playernum, colorblind_lobby, false));
10423 }
10424
10425 controllerFrame->setSize(hudSize);
10426 hudFrame->setSize(hudSize);
10427
10428 if ( gamePaused || nohud || !players[player.playernum]->isLocalPlayer() )
10429 {
10430 // hide
10431 hudFrame->setDisabled(true);
10432 controllerFrame->setDisabled(true);
10433 }
10434 else
10435 {
10436 hudFrame->setDisabled(false);
10437 if ( !controllerFrame->isHollow() )
10438 {
10439 controllerFrame->setDisabled(false);
10440 }
10441 }
10442
10443 static ConsoleVariable<bool> cvar_disable_controller_reconnect("/disable_controller_reconnect", false);
10444 if ( !*cvar_disable_controller_reconnect )
10445 {
10446 if ( MainMenu::isPlayerSignedIn(player.playernum) && players[player.playernum]->isLocalPlayer() )
10447 {
10448 checkControllerState(player.playernum);
10449 }
10450 }
10451 static ConsoleVariable<int> cvar_ui_above_hotbar_y("/ui_above_hotbar_y", 56);
10452 offsetHUDAboveHotbarHeight = 0;
10453 if ( player.bUseCompactGUIWidth() )
10454 {
10455#ifndef NINTENDO
10456 if ( inputs.hasController(player.playernum) )
10457 {
10458 if ( !playerSettings[multiplayer ? 0 : player.playernum].gamepad_facehotbar || *cvar_hotbar_compact_disable )
10459 {
10460 offsetHUDAboveHotbarHeight = *cvar_ui_above_hotbar_y;
10461 }
10462 }
10463 else if ( inputs.bPlayerUsingKeyboardControl(player.playernum) )
10464 {
10465 offsetHUDAboveHotbarHeight = *cvar_ui_above_hotbar_y;
10466 }
10467#else
10468 if ( !playerSettings[multiplayer ? 0 : player.playernum].gamepad_facehotbar || *cvar_hotbar_compact_disable )
10469 {
10470 offsetHUDAboveHotbarHeight = *cvar_ui_above_hotbar_y;
10471 }
10472#endif // NINTENDO
10473 }
10474
10475 if ( !gamePaused && (player.entity || player.ghost.isActive()) && player.shootmode)
10476 {
10477 inputs.getVirtualMouse(player.playernum)->draw_cursor = false;
10478 }
10479
10480 if ( !this->minimapFrame )
10481 {
10482 this->minimapFrame = createMinimap(player.playernum);
10483 }
10484 if ( !mapPromptFrame )
10485 {
10486 createMapPromptFrame(player.playernum);
10487 }
10488 if ( !gameTimerFrame )
10489 {
10490 createGameTimerFrame(player.playernum);
10491 }
10492 if ( !xpFrame )
10493 {
10494 createXPBar(player.playernum);
10495 }
10496 if ( !hpFrame )
10497 {
10498 createHPMPBars(player.playernum);
10499 }
10500 if ( !calloutPromptFrame )
10501 {
10502 createCalloutPromptFrame(player.playernum);
10503 }
10504 if ( !enemyBarFrame )
10505 {
10506 createEnemyBar(player.playernum, enemyBarFrame);
10507 }
10508 if ( !enemyBarFrameHUD )
10509 {
10510 createEnemyBar(player.playernum, enemyBarFrameHUD);
10511 }
10512 if ( !StatusEffectQueue[player.playernum].statusEffectFrame )
10513 {
10514 createStatusEffectQueue(player.playernum);
10515 }
10516 if ( !allyFollowerTitleFrame )
10517 {
10518 createAllyFollowerTitleFrame(player.playernum);
10519 }
10520 if ( !allyFollowerFrame )
10521 {
10522 createAllyFollowerFrame(player.playernum);
10523 }
10524 if ( !allyPlayerFrame )
10525 {
10526 createAllyPlayerFrame(player.playernum);
10527 }
10528
10529 updateMinimapPrompts();
10530 updateGameTimer();
10531 updateAllyFollowerFrame(player.playernum);
10532 updateAllyPlayerFrame(player.playernum);
10533 updateXPBar();
10534 updateHPBar();
10535 updateMPBar();
10536 updateCalloutPromptFrame(player.playernum);
10537 updateActionPrompts();
10538 updateUINavigation();
10539 enemyHPDamageBarHandler[player.playernum].cullExpiredHPBars();
10540 enemyBarFrame->setDisabled(true);
10541 enemyBarFrameHUD->setDisabled(true);
10542 for ( auto& HPBar : enemyHPDamageBarHandler[player.playernum].HPBars )
10543 {
10544 updateEnemyBar2(enemyBarFrame, &HPBar.second);
10545 }
10546 updateStatusEffectQueue(player.playernum);
10547}
10548
10549void Player::MessageZone_t::createChatbox()
10550{
10551 char name[32];
10552 snprintf(name, sizeof(name), "player chat %d", player.playernum);
10553 if ( !gameUIFrame[player.playernum]->findFrame(name) )
10554 {
10555 Frame* chatMainFrame = gameUIFrame[player.playernum]->addFrame(name);
10556 chatMainFrame->setHollow(true);
10557 chatMainFrame->setBorder(0);
10558 chatMainFrame->setOwner(player.playernum);
10559 chatMainFrame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(),
10560 players[player.playernum]->camera_virtualy1(),
10561 players[player.playernum]->camera_virtualWidth(),
10562 players[player.playernum]->camera_virtualHeight() });
10563 chatFrame = chatMainFrame;
10564 Frame* messages = chatMainFrame->addFrame("message box");
10565 messages->setHollow(true);
10566 messages->setInheritParentFrameOpacity(false);
10567
10568 static const char* bigfont = "fonts/pixelmix.ttf#16#2";
10569 SDL_Rect entryPos{ 0, 0, messages->getSize().w, messages->getSize().h };
10570 for ( int i = 0; i < MESSAGE_MAX_ENTRIES; ++i )
10571 {
10572 char msgName[32];
10573 snprintf(msgName, sizeof(msgName), "message %d", i);
10574 auto entry = messages->addField(msgName, ADD_MESSAGE_BUFFER_LENGTH);
10575 entry->setFont(bigfont);
10576 entry->setSize(entryPos);
10577 entry->setDisabled(true);
10578 entry->setVJustify(Field::justify_t::TOP);
10579 }
10580 }
10581}
10582
10583static ConsoleVariable<int> cvar_log_lineheight_offset("/log_lineheight_offset", -6);
10584static ConsoleVariable<int> cvar_log_lineheight_min("/log_lineheight_minimum", 24);
10585static ConsoleVariable<int> cvar_log_multiline_pady("/log_multiline_pady", -4);
10586const char* Player::MessageZone_t::bigfont = "fonts/pixelmix.ttf#16#2";
10587const char* Player::MessageZone_t::smallfont = "fonts/pixel_maz_multiline.ttf#16#2";
10588static ConsoleVariable<int> cvar_message_fade_min("/message_fade_min", 25);
10589void Player::MessageZone_t::processChatbox()
10590{
10591 if (!chatFrame) {
10592 createChatbox();
10593 }
10594
10595 chatFrame->setSize(SDL_Rect{ player.camera_virtualx1(),
10596 player.camera_virtualy1(),
10597 player.camera_virtualWidth(),
10598 player.camera_virtualHeight() });
10599
10600 int playercount = 0;
10601 for (int c = 0; c < MAXPLAYERS4; ++c) {
10602 if (!client_disconnected[c] && players[c]->isLocalPlayer()) {
10603 ++playercount;
10604 }
10605 }
10606
10607 Frame* messageBoxFrame = chatFrame->findFrame("message box");
10608 messageBoxFrame->setDisabled(gamePaused || (!player.shootmode && playercount > 2));
10609 if (messageBoxFrame->isDisabled()) {
10610 return;
10611 }
10612
10613 static const char* bigfont = "fonts/pixelmix.ttf#16#2";
10614 static const char* smallfont = "fonts/pixel_maz_multiline.ttf#16#2";
10615 static ConsoleVariable<bool> cvar_smallmessages("/smallmessages", false);
10616 //static ConsoleVariable<bool> cvar_top_aligned("/topmessages", false);
10617 //static ConsoleVariable<std::string> alignment("/alignmessages", "");
10618 static ConsoleVariable<std::string> cvar_setalignment("/alignmessages", "");
10619 static ConsoleVariable<int> cvar_messages_left_y("/messages_left_y", 200);
10620 useBigFont = !player.bUseCompactGUIHeight() && !player.bUseCompactGUIWidth();// || (playercount == 2 && !*MainMenu::vertical_splitscreen);
10621 if ( *cvar_smallmessages )
10622 {
10623 useBigFont = false;
10624 }
10625
10626 if ( *cvar_setalignment == "left" )
10627 {
10628 messageAlignment = ALIGN_LEFT_BOTTOM;
10629 }
10630 else if ( *cvar_setalignment == "top" )
10631 {
10632 messageAlignment = ALIGN_LEFT_TOP;
10633 }
10634 else if ( *cvar_setalignment == "center" )
10635 {
10636 messageAlignment = ALIGN_CENTER_BOTTOM;
10637 }
10638
10639 actualAlignment = ALIGN_LEFT_BOTTOM;
10640 if ( player.bUseCompactGUIHeight() && player.bUseCompactGUIWidth() )
10641 {
10642 actualAlignment = ALIGN_LEFT_TOP;
10643 }
10644 else if ( messageAlignment == ALIGN_CENTER_BOTTOM )
10645 {
10646 if ( !player.bUseCompactGUIHeight() )
10647 {
10648 // allowed in tall or singleplayer
10649 actualAlignment = messageAlignment;
10650 }
10651 else
10652 {
10653 actualAlignment = ALIGN_LEFT_TOP; // else default to top-left in wide 2-player
10654 if ( !player.shootmode
10655 && (player.gui_mode == GUI_MODE_INVENTORY || player.gui_mode == GUI_MODE_SHOP) )
10656 {
10657 actualAlignment = ALIGN_LEFT_BOTTOM;
10658 }
10659 }
10660 }
10661 else if ( messageAlignment == ALIGN_LEFT_BOTTOM )
10662 {
10663 actualAlignment = messageAlignment; // ok in all configs
10664 }
10665 else if ( messageAlignment == ALIGN_LEFT_TOP )
10666 {
10667 actualAlignment = messageAlignment;
10668 if ( playercount <= 2 && !player.shootmode
10669 && (player.gui_mode == GUI_MODE_INVENTORY || player.gui_mode == GUI_MODE_SHOP) )
10670 {
10671 actualAlignment = ALIGN_LEFT_BOTTOM;
10672 }
10673 }
10674
10675 switch ( actualAlignment )
10676 {
10677 case ALIGN_CENTER_BOTTOM:
10678 bottomAlignedMessages = true;
10679 break;
10680 case ALIGN_LEFT_BOTTOM:
10681 bottomAlignedMessages = true;
10682 break;
10683 case ALIGN_LEFT_TOP:
10684 bottomAlignedMessages = false;
10685 break;
10686 default:
10687 break;
10688 }
10689
10690 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
10691 if ( player.bUseCompactGUIWidth() && actualAlignment == ALIGN_LEFT_TOP &&
10692 ((player.hud.levelupFrame && !player.hud.levelupFrame->isDisabled())
10693 || (player.hud.skillupFrame && !player.hud.skillupFrame->isDisabled())) )
10694 {
10695 animFade -= fpsScale * std::max(.1, (animFade)) / (2.5);
10696 animFade = std::max(0.0, animFade);
10697 }
10698 else
10699 {
10700 animFade += fpsScale * std::max(.1, (1.0 - animFade)) / (5.0);
10701 animFade = std::min(1.0, animFade);
10702 }
10703
10704 messageBoxFrame->setOpacity(*cvar_message_fade_min + (100.0 - *cvar_message_fade_min) * animFade);
10705
10706 bool pushPaddingX = !player.shootmode
10707 && actualAlignment != ALIGN_CENTER_BOTTOM
10708 && (player.gui_mode == GUI_MODE_INVENTORY || player.gui_mode == GUI_MODE_SHOP)
10709 && ((playercount == 1
10710 && player.inventoryUI.getSizeY() > player.inventoryUI.DEFAULT_INVENTORY_SIZEY) ||
10711 (playercount == 2 && !*MainMenu::vertical_splitscreen));
10712
10713 auto& messageLayoutSetting = messageZoneSettings.getLayout(player.playernum, actualAlignment);
10714 MESSAGE_MAX_ENTRIES = messageLayoutSetting.maxMessages;
10715
10716 const int leftAlignedPaddingX = pushPaddingX ? 240 : 8;
10717 int leftAlignedBottomY = *cvar_messages_left_y;
10718 leftAlignedBottomY += messageLayoutSetting.offsetY;
10719 if ( player.bUseCompactGUIHeight() )
10720 {
10721 if ( StatusEffectQueue[player.playernum].statusEffectFrame )
10722 {
10723 const int hungerIconSize = 64;
10724 if ( actualAlignment == ALIGN_LEFT_BOTTOM )
10725 {
10726 leftAlignedBottomY = players[player.playernum]->camera_virtualy2()
10727 - (StatusEffectQueue[player.playernum].statusEffectFrame->getSize().y
10728 + StatusEffectQueue[player.playernum].statusEffectFrame->getSize().h
10729 - hungerIconSize - 8);
10730 }
10731 }
10732 }
10733 const int topAlignedPaddingX = 8;
10734 int topAlignedPaddingY = 8;
10735 if ( player.hud.allyPlayerFrame && actualAlignment == ALIGN_LEFT_TOP && !splitscreen )
10736 {
10737 SDL_Rect allyPlayerPos = player.hud.allyPlayerFrame->getSize();
10738 if ( !player.hud.allyPlayerFrame->isDisabled() && allyPlayerPos.h > 0 )
10739 {
10740 topAlignedPaddingY = 4 + allyPlayerPos.y + allyPlayerPos.h;
10741 }
10742 }
10743 if ( player.hud.xpFrame && actualAlignment == ALIGN_LEFT_TOP )
10744 {
10745 SDL_Rect xpFramePos = player.hud.xpFrame->getSize();
10746 topAlignedPaddingY = std::max(topAlignedPaddingY, 4 + std::max(xpFramePos.y, 0) + xpFramePos.h);
10747 if ( !(player.bUseCompactGUIHeight() && player.bUseCompactGUIWidth()) )
10748 {
10749 topAlignedPaddingY += 4;
10750 }
10751 }
10752 SDL_Rect messageboxTopAlignedPos{
10753 topAlignedPaddingX,
10754 topAlignedPaddingY,
10755 player.camera_virtualWidth() - topAlignedPaddingX * 2,
10756 player.camera_virtualHeight() - topAlignedPaddingY };
10757 SDL_Rect messageboxLeftAlignedPos{
10758 leftAlignedPaddingX,
10759 0,
10760 player.camera_virtualWidth() - leftAlignedPaddingX * 2,
10761 player.camera_virtualHeight() - leftAlignedBottomY };
10762
10763
10764 SDL_Rect messageBoxSize = bottomAlignedMessages ?
10765 messageboxLeftAlignedPos : messageboxTopAlignedPos;
10766
10767 for ( int i = 0; i < MESSAGE_MAX_ENTRIES; ++i )
10768 {
10769 char msgName[32];
10770 snprintf(msgName, sizeof(msgName), "message %d", i);
10771 if ( auto entry = messageBoxFrame->findField(msgName) )
10772 {
10773 entry->setDisabled(true);
10774
10775 // set alignment
10776 if ( actualAlignment == ALIGN_CENTER_BOTTOM ) {
10777 entry->setHJustify(Field::justify_t::CENTER);
10778 } else {
10779 entry->setHJustify(Field::justify_t::LEFT);
10780 }
10781 }
10782 }
10783
10784 const bool messageDrawDescending = (actualAlignment == ALIGN_LEFT_TOP);
10785 const int entryPaddingY = playercount > 2 ? 0 : 4;
10786
10787 int currentY = messageDrawDescending ?
10788 0 : messageBoxSize.h;
10789
10790 int index = 0;
10791 int currentline = 0;
10792
10793 auto it = notification_messages.begin();
10794 auto end = notification_messages.end();
10795 auto rit = notification_messages.rbegin();
10796 auto rend = notification_messages.rend();
10797 int textLinePadding = useBigFont ? 0 : -4;
10798 for ( ; messageDrawDescending ? rit != rend : it != end; ++it, ++rit )
10799 {
10800 Message* current = messageDrawDescending ? *rit : *it;
10801 if ( currentline >= MESSAGE_MAX_ENTRIES)
10802 {
10803 break;
10804 }
10805
10806 Uint32 color = (current->text->color & 0x00ffffff) | ((Uint32)current->alpha << 24);
10807
10808 char msgName[32];
10809 snprintf(msgName, sizeof(msgName), "message %d", index);
10810 if ( auto entry = messageBoxFrame->findField(msgName) )
10811 {
10812 entry->setDisabled(false);
10813 entry->setColor(color);
10814
10815 std::string data = messageSanitizePercentSign(current->text->data, nullptr);
10816 char str[ADD_MESSAGE_BUFFER_LENGTH];
10817 snprintf(str, sizeof(str), data.c_str());
10818
10819 entry->setText(str);
10820 entry->setFont(useBigFont ? bigfont : smallfont);
10821 entry->setPaddingPerLine(useBigFont ? 0 : *cvar_log_multiline_pady);
10822 Font* fontGet = Font::get(entry->getFont());
10823 Text* textGet = entry->getTextObject();
10824
10825 int w = textGet->getWidth();
10826 int h = textGet->getHeight() * (current->text->lines + textLinePadding);
10827 int textHeight = h;
10828 if ( !useBigFont )
10829 {
10830 int h2 = (int)(std::max(*cvar_log_lineheight_min + 2,
10831 ((int)textGet->getHeight() + textLinePadding) * textGet->getNumTextLines() + textLinePadding));
10832 textHeight = h2;
10833 h = textHeight + *cvar_log_lineheight_offset;
10834 }
10835
10836 if (!messageDrawDescending) {
10837 currentY -= h;
10838 currentY -= entryPaddingY;
10839 }
10840
10841 SDL_Rect pos = entry->getSize();
10842 pos.x = 0;
10843 pos.y = currentY;
10844 pos.w = messageBoxFrame->getSize().w;
10845 pos.h = textHeight;
10846 entry->setSize(pos);
10847
10848 if (messageDrawDescending) {
10849 currentY += h;
10850 currentY += entryPaddingY;
10851 }
10852 }
10853 ++index;
10854 currentline += current->text->lines;
10855 }
10856
10857 messageBoxFrame->setSize(messageBoxSize);
10858}
10859
10860ConsoleVariable<bool> shareMinimap("/shareminimap", true);
10861ConsoleVariable<bool> cvar_minimap_prompt_vertical("/minimap_prompt_vertical", false);
10862Frame* minimapFrame = nullptr; // shared minimap
10863SDL_Rect Player::Minimap_t::sharedMinimapPos{ 0, 0, 0, 0 };
10864int Player::Minimap_t::fullSize = 200;
10865int Player::Minimap_t::compactSize = 200;
10866int Player::Minimap_t::compact2pVerticalSize = 200;
10867real_t Player::Minimap_t::fullBigScale = 100.0;
10868real_t Player::Minimap_t::compactBigScale = 100.0;
10869real_t Player::Minimap_t::compact2pVerticalBigScale = 100.0;
10870bool Player::Minimap_t::bUpdateMainMenuSettingScale = false;
10871real_t Player::Minimap_t::mainMenuSettingScale = 1.0;
10872
10873void doSharedMinimap() {
10874 if (!minimapFrame) {
10875 minimapFrame = gui->addFrame("shared_minimap");
10876 minimapFrame->setColor(0);
10877 minimapFrame->setHollow(true);
10878 minimapFrame->setInvisible(true);
10879 minimapFrame->setDrawCallback([](const Widget& widget, SDL_Rect rect){
10880 drawMinimap(widget.getOwner(), rect, true);
10881 });
10882 minimapFrame->setTickCallback([](Widget& widget){
10883 int playercount = 0;
10884 for (int c = 0; c < MAXPLAYERS4; ++c) {
10885 if (!client_disconnected[c] && players[c]->isLocalPlayer()) {
10886 ++playercount;
10887 }
10888 }
10889 if (gamePaused || intro || MainMenu::isCutsceneActive() || playercount < 3 || !*shareMinimap) {
10890 minimapFrame->setInvisible(true);
10891 } else {
10892 minimapFrame->setInvisible(false);
10893 }
10894 if (playercount == 3) {
10895 const int size = std::min(Frame::virtualScreenX / 2, Frame::virtualScreenY / 2);
10896 minimapFrame->setSize(SDL_Rect{
10897 (Frame::virtualScreenX + ((Frame::virtualScreenX / 2) - size)) / 2,
10898 (Frame::virtualScreenY + ((Frame::virtualScreenY / 2) - size)) / 2,
10899 size, size});
10900 } else if (playercount == 4) {
10901 constexpr int size = 128;
10902 minimapFrame->setSize(SDL_Rect{
10903 (Frame::virtualScreenX - size) / 2,
10904 Frame::virtualScreenY / 2,
10905 size, size});
10906 }
10907 Player::Minimap_t::sharedMinimapPos = static_cast<Frame*>(&widget)->getSize();
10908 });
10909 }
10910 minimapFrame->setOwner(clientnum);
10911}
10912
10913static Frame* createMinimap(int player) {
10914 std::string name = "minimap";
10915 name.append(std::to_string(player));
10916 auto& minimap = players[player]->minimap;
10917 minimap.real_scale = minimapScale;
10918 minimap.scale = minimapScale;
10919 Frame* parent = players[player]->hud.hudFrame;
10920 Frame* window = parent->addFrame(name.c_str());
10921 window->setSize(SDL_Rect{0, 0, 0, 0});
10922 window->setColor(0);
10923 window->setOwner(player);
10924 window->setDrawCallback([](const Widget& widget, SDL_Rect rect){
10925 drawMinimap(widget.getOwner(), rect, false);
10926 });
10927
10928 window->setTickCallback([](Widget& widget){
10929 int playercount = 0;
10930 for (int c = 0; c < MAXPLAYERS4; ++c) {
10931 if (!client_disconnected[c] && players[c]->isLocalPlayer()) {
10932 ++playercount;
10933 }
10934 }
10935
10936 widget.setInvisible(*shareMinimap && playercount > 2);
10937 auto player = widget.getOwner();
10938 auto& input = Input::inputs[player];
10939 auto& minimap = players[player]->minimap;
10940 bool reducedSize = playercount > 2 /*|| (playercount == 2 && *MainMenu::vertical_splitscreen)*/;
10941
10942 minimap.bExpandPromptEnabled = true;
10943 minimap.bScalePromptEnabled = false;
10944 if ( !players[player]->shootmode )
10945 {
10946 minimap.bExpandPromptEnabled = false;
10947 minimap.bScalePromptEnabled = false;
10948 }
10949 else if ( players[player]->worldUI.isEnabled()
10950 && players[player]->worldUI.bTooltipInView
10951 && players[player]->worldUI.tooltipsInRange.size() > 1 )
10952 {
10953 const std::string scaleBinding = input.binding("Minimap Scale");
10954 const std::string expandBinding = input.binding("Toggle Minimap");
10955 const std::string cycleNextBinding = input.binding("Interact Tooltip Next");
10956 const std::string cyclePrevBinding = input.binding("Interact Tooltip Prev");
10957 if ( scaleBinding == cycleNextBinding
10958 || scaleBinding == cyclePrevBinding )
10959 {
10960 minimap.bScalePromptEnabled = false;
10961 }
10962 if ( expandBinding == cycleNextBinding
10963 || expandBinding == cyclePrevBinding )
10964 {
10965 minimap.bExpandPromptEnabled = false;
10966 }
10967 }
10968
10969 if ( !gamePaused && players[player]->bControlEnabled && minimap.bScalePromptEnabled
10970 && !players[player]->usingCommand() && players[player]->shootmode && input.consumeBinaryToggle("Minimap Scale")) {
10971 if (minimap.scale > 75.0) {
10972 minimap.real_scale = 75.0;
10973 }
10974 else if (minimap.scale > 50.0) {
10975 minimap.real_scale = 50.0;
10976 }
10977 else if (minimap.scale > 25.0) {
10978 minimap.real_scale = 25.0;
10979 }
10980 else {
10981 minimap.real_scale = reducedSize ? 75.0 : 100.0;
10982 }
10983 }
10984
10985 auto& scale_ang = minimap.scale_ang;
10986 auto& scale = minimap.scale;
10987 minimap.animating = true;
10988 if (minimap.big) {
10989 if (scale_ang < PI3.14159265358979323846 / 2.0) {
10990 scale_ang += ((PI3.14159265358979323846 / 144.0) * 2.0) * getFPSScale(144.0);
10991 if (scale_ang > PI3.14159265358979323846 / 2.0) {
10992 scale_ang = PI3.14159265358979323846 / 2.0;
10993 }
10994 }
10995 if ( scale_ang >= PI3.14159265358979323846 / 2.0 )
10996 {
10997 minimap.animating = false;
10998 }
10999 } else {
11000 if (scale_ang > 0.0) {
11001 scale_ang -= ((PI3.14159265358979323846 / 144.0) * 2.0) * getFPSScale(144.0);
11002 if (scale_ang < 0.0) {
11003 scale_ang = 0.0;
11004 }
11005 }
11006 if ( scale_ang <= 0.0 )
11007 {
11008 minimap.animating = false;
11009 }
11010 }
11011
11012 real_t factor0 = 1.0 - sin(scale_ang);
11013 real_t factor1 = sin(scale_ang);
11014 //real_t scale_small = std::min(reducedSize ? 25.0 : 50.0, minimap.real_scale);
11015 //real_t scale_big = std::min(reducedSize ? 75.0 : 100.0, minimap.real_scale);
11016 real_t scale_small = 50.0;
11017 real_t scale_big = Player::Minimap_t::fullBigScale;
11018 int maxSize = Player::Minimap_t::fullSize;
11019 static ConsoleVariable<int> cvar_minimap_compact_offset_y("/minimap_compact_offset_y", 44);
11020 int mapBigOffsetY = 0;
11021 if ( players[player]->bUseCompactGUIHeight() ) // 2p wide
11022 {
11023 maxSize = Player::Minimap_t::compactSize;
11024 scale_big = Player::Minimap_t::compactBigScale;
11025 mapBigOffsetY = *cvar_minimap_compact_offset_y;
11026 }
11027 else if ( !players[player]->bUseCompactGUIHeight() && players[player]->bUseCompactGUIWidth() ) // 2p vertical
11028 {
11029 maxSize = Player::Minimap_t::compact2pVerticalSize;
11030 scale_big = Player::Minimap_t::compact2pVerticalBigScale;
11031 }
11032
11033 {
11034 real_t scale = factor0 * scale_small;
11035 players[player]->minimap.minimapPos.w = (int)((scale / 100.0) * maxSize);
11036 players[player]->minimap.minimapPos.h = (int)((scale / 100.0) * maxSize);
11037 }
11038
11039 scale = factor0 * scale_small + factor1 * scale_big;
11040
11041 Frame* parent = players[player]->hud.hudFrame;
11042
11043 int mapHeightOffset = 0;
11044 if ( players[player]->hud.mapPromptFrame && !players[player]->hud.mapPromptFrame->isDisabled() )
11045 {
11046 mapHeightOffset = players[player]->hud.mapPromptFrame->getSize().h;
11047 }
11048 else if ( players[player]->hud.gameTimerFrame && !players[player]->hud.gameTimerFrame->isDisabled()
11049 // ignore if ui raised above hotbar and vertical split layout
11050 && (!(players[player]->hud.offsetHUDAboveHotbarHeight > 0 && splitscreen && !players[player]->bUseCompactGUIHeight() && players[player]->bUseCompactGUIWidth())) )
11051 {
11052 mapHeightOffset = players[player]->hud.gameTimerFrame->getSize().h;
11053 }
11054 mapHeightOffset += players[player]->hud.offsetHUDAboveHotbarHeight;
11055
11056 const int scaledSize = (int)((scale / 100.0) * maxSize);
11057 int x = factor0 * (parent->getSize().w - scaledSize) +
11058 factor1 * (parent->getSize().w - scaledSize) / 2;
11059 int y = factor0 * (parent->getSize().h - scaledSize - mapHeightOffset) +
11060 factor1 * (parent->getSize().h - scaledSize - mapBigOffsetY) / 2;
11061
11062 auto frame = static_cast<Frame*>(&widget);
11063 frame->setSize(SDL_Rect{x, y, (int)(scaledSize), (int)(scaledSize)});
11064 players[player]->minimap.minimapPos.x = x;
11065 players[player]->minimap.minimapPos.y = y;
11066 if ( frame->isInvisible() )
11067 {
11068 frame->setHollow(true);
11069 }
11070 else
11071 {
11072 frame->setHollow(true);
11073 }
11074 });
11075
11076 return window;
11077}
11078
11079void openMinimap(int player) {
11080 Frame* minimap = players[player]->hud.minimapFrame;
11081 if (minimap) {
11082 players[player]->minimap.big = (players[player]->minimap.big==false);
11083 }
11084}
11085
11086void Player::MessageZone_t::processLogFrame()
11087{
11088 if ( !logParentFrame )
11089 {
11090 Frame* f = gameUIFrame[player.playernum]->addFrame("log parent frame");
11091 f->setHollow(true);
11092 f->setDisabled(true);
11093 f->setBorder(0);
11094 f->setOwner(player.playernum);
11095 logParentFrame = f;
11096 }
11097
11098 if ( logWindow )
11099 {
11100 logParentFrame->setDisabled(false);
11101 }
11102 else
11103 {
11104 if ( player.hud.hudFrame )
11105 {
11106 logParentFrame->setDisabled(player.hud.hudFrame->isDisabled());
11107 }
11108 else
11109 {
11110 logParentFrame->setDisabled(true);
11111 }
11112 }
11113
11114 if ( !logParentFrame->isDisabled() )
11115 {
11116 logParentFrame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(),
11117 players[player.playernum]->camera_virtualy1(),
11118 players[player.playernum]->camera_virtualWidth(),
11119 players[player.playernum]->camera_virtualHeight() });
11120 }
11121}
11122void Player::Minimap_t::processMapFrame()
11123{
11124 if ( !mapParentFrame )
11125 {
11126 Frame* f = gameUIFrame[player.playernum]->addFrame("map parent frame");
11127 f->setHollow(true);
11128 f->setDisabled(true);
11129 f->setBorder(0);
11130 f->setOwner(player.playernum);
11131 mapParentFrame = f;
11132 }
11133
11134 if ( mapWindow )
11135 {
11136 mapParentFrame->setDisabled(false);
11137 }
11138 else
11139 {
11140 if ( player.hud.hudFrame )
11141 {
11142 mapParentFrame->setDisabled(player.hud.hudFrame->isDisabled());
11143 }
11144 else
11145 {
11146 mapParentFrame->setDisabled(true);
11147 }
11148 }
11149
11150 if ( !mapParentFrame->isDisabled() )
11151 {
11152 mapParentFrame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(),
11153 players[player.playernum]->camera_virtualy1(),
11154 players[player.playernum]->camera_virtualWidth(),
11155 players[player.playernum]->camera_virtualHeight() });
11156 }
11157}
11158
11159void openMapWindow(int player) {
11160 if ( !players[player]->minimap.mapParentFrame )
11161 {
11162 return;
11163 }
11164 auto& frame = players[player]->minimap.mapWindow;
11165 if (frame) {
11166 frame->removeSelf();
11167 frame = nullptr;
11168 if ( players[player]->gui_mode == GUI_MODE_NONE ) {
11169 players[player]->shootmode = true;
11170 }
11171 else
11172 {
11173 players[player]->GUI.returnToPreviousActiveModule();
11174 }
11175 Player::soundCancel();
11176 return;
11177 }
11178
11179 players[player]->GUI.previousModule = players[player]->GUI.activeModule;
11180 bool bOldShootmode = players[player]->shootmode;
11181 if ( players[player]->shootmode )
11182 {
11183 players[player]->openStatusScreen(GUI_MODE_NONE,
11184 INVENTORY_MODE_ITEM, Player::GUI_t::MODULE_MAP);
11185 }
11186 else
11187 {
11188 players[player]->openStatusScreen(GUI_MODE_INVENTORY,
11189 players[player]->inventory_mode, Player::GUI_t::MODULE_MAP); // Reset the GUI to the inventory.
11190 }
11191
11192 Player::soundActivate();
11193
11194 auto& otherWindow = players[player]->messageZone.logWindow;
11195 if (otherWindow) {
11196 otherWindow->removeSelf();
11197 otherWindow = nullptr;
11198 }
11199 Frame* parent = players[player]->minimap.mapParentFrame;
11200 const SDL_Rect size = parent->getSize();
11201 int _w = std::max(Frame::virtualScreenX / 2, size.w - 432);
11202 int _h = std::max(Frame::virtualScreenY / 2, size.h - 256);
11203 int yoffset = 32;
11204 const bool bCompact = players[player]->bUseCompactGUIHeight() || players[player]->bUseCompactGUIWidth();
11205 if ( bCompact )
11206 {
11207 if ( players[player]->bUseCompactGUIHeight() && players[player]->bUseCompactGUIWidth() )
11208 {
11209 static ConsoleVariable<int> cvar_map_splitscreen_w("/map_splitscreen_wborder", 64);
11210 static ConsoleVariable<int> cvar_map_splitscreen_h("/map_splitscreen_hborder", 16);
11211 static ConsoleVariable<int> cvar_map_splitscreen_offset_y("/map_splitscreen_offset_y", 0);
11212 _w = size.w - *cvar_map_splitscreen_w;
11213 _h = size.h - *cvar_map_splitscreen_h;
11214 yoffset = *cvar_map_splitscreen_offset_y;
11215 }
11216 else if ( players[player]->bUseCompactGUIHeight() )
11217 {
11218 static ConsoleVariable<int> cvar_map_splitscreen_2p_wide_w("/map_splitscreen_2p_wide_wborder", 64);
11219 static ConsoleVariable<int> cvar_map_splitscreen_2p_wide_h("/map_splitscreen_2p_wide_hborder", 8);
11220 static ConsoleVariable<int> cvar_map_splitscreen_2p_wide_offset_y("/map_splitscreen_2p_wide_offset_y", 0);
11221 _w = size.w - *cvar_map_splitscreen_2p_wide_w;
11222 _h = size.h - *cvar_map_splitscreen_2p_wide_h;
11223 yoffset = *cvar_map_splitscreen_2p_wide_offset_y;
11224 }
11225 else if ( players[player]->bUseCompactGUIWidth() )
11226 {
11227 static ConsoleVariable<int> cvar_map_splitscreen_2p_tall_w("/map_splitscreen_2p_tall_wborder", 64);
11228 static ConsoleVariable<int> cvar_map_splitscreen_2p_tall_h("/map_splitscreen_2p_tall_hborder", 64);
11229 static ConsoleVariable<int> cvar_map_splitscreen_2p_wide_offset_y("/map_splitscreen_2p_tall_offset_y", 0);
11230 if ( *MainMenu::clipped_splitscreen )
11231 {
11232 *cvar_map_splitscreen_2p_tall_h = 156;
11233 *cvar_map_splitscreen_2p_wide_offset_y = 2;
11234 }
11235 else
11236 {
11237 *cvar_map_splitscreen_2p_tall_h = 256;
11238 *cvar_map_splitscreen_2p_wide_offset_y = 32;
11239 }
11240 _w = size.w - *cvar_map_splitscreen_2p_tall_w;
11241 _h = size.h - *cvar_map_splitscreen_2p_tall_h;
11242 yoffset = *cvar_map_splitscreen_2p_wide_offset_y;
11243 }
11244 }
11245 int w = std::min(_w, _h);
11246 int h = std::min(_w, _h);
11247 frame = parent->addFrame("minimap_window");
11248 frame->setOwner(player);
11249 frame->setSize(SDL_Rect{(size.w - w) / 2, (size.h - h) / 2 - yoffset, w, h});
11250 frame->setBorderColor(makeColor(51, 33, 26, 255));
11251 frame->setColor(0);
11252 //frame->setBorder(2);
11253 frame->setBorder(0);
11254 frame->setColor(makeColor(mapBgColor->x, mapBgColor->y, mapBgColor->z, mapBgColor->w));
11255 frame->setTickCallback([](Widget& widget){
11256 const int player = widget.getOwner();
11257 auto frame = static_cast<Frame*>(&widget);
11258 if (players[player]->shootmode) {
11259 players[player]->minimap.mapWindow = nullptr;
11260 frame->removeSelf();
11261 }
11262 });
11263
11264 // frame images
11265 {
11266 frame->addImage(
11267 SDL_Rect{0, 0, 16, 32},
11268 0xffffffff,
11269 "*#images/ui/MapAndLog/Hover_TL00.png",
11270 "TL");
11271 frame->addImage(
11272 SDL_Rect{16, 0, w - 32, 32},
11273 0xffffffff,
11274 "*#images/ui/MapAndLog/Hover_T00.png",
11275 "T");
11276 frame->addImage(
11277 SDL_Rect{w - 16, 0, 16, 32},
11278 0xffffffff,
11279 "*#images/ui/MapAndLog/Hover_TR00.png",
11280 "TR");
11281 auto L = frame->addImage(
11282 SDL_Rect{0, 32, 4, h - 48},
11283 0xffffffff,
11284 "*#images/ui/MapAndLog/Hover_L00.png",
11285 "L");
11286 L->ontop = true;
11287 auto R = frame->addImage(
11288 SDL_Rect{w - 4, 32, 4, h - 48},
11289 0xffffffff,
11290 "*#images/ui/MapAndLog/Hover_R00.png",
11291 "R");
11292 R->ontop = true;
11293 auto BL = frame->addImage(
11294 SDL_Rect{0, h - 16, 16, 16},
11295 0xffffffff,
11296 "*#images/ui/MapAndLog/Hover_BL00.png",
11297 "BL");
11298 BL->ontop = true;
11299 auto B = frame->addImage(
11300 SDL_Rect{16, h - 4, w - 32, 4},
11301 0xffffffff,
11302 "*#images/ui/MapAndLog/Hover_B00.png",
11303 "B");
11304 B->ontop = true;
11305 auto BR = frame->addImage(
11306 SDL_Rect{w - 16, h - 16, 16, 16},
11307 0xffffffff,
11308 "*#images/ui/MapAndLog/Hover_BR00.png",
11309 "BR");
11310 BR->ontop = true;
11311 }
11312
11313 auto container = frame->addFrame("container");
11314 container->setSize(SDL_Rect{0, 32, w, h - 32});
11315 container->setBorderColor(makeColor(51, 33, 26, 255));
11316 //container->setBorder(2);
11317 container->setBorder(0);
11318 container->setColor(0);
11319
11320 const int map_size = std::min(w - 32, h - 64);
11321 auto minimap = container->addFrame("minimap");
11322 minimap->setSize(SDL_Rect{(w - map_size) / 2, (h - 32 - map_size) / 2, map_size, map_size});
11323 minimap->setColor(0);
11324 minimap->setBorder(0);
11325
11326 struct Position {
11327 real_t x;
11328 real_t y;
11329 };
11330 static Position minimap_cursor[MAXPLAYERS4];
11331 minimap_cursor[player].x = map_size / 2;
11332 minimap_cursor[player].y = map_size / 2;
11333
11334 if ( !bOldShootmode && players[player]->GUI.previousModule == Player::GUI_t::MODULE_CHARACTERSHEET )
11335 {
11336 if ( !inputs.getVirtualMouse(player)->draw_cursor )
11337 {
11338 Input::inputs[player].consumeBinary("MinimapPing"); // clicked from button
11339 }
11340 }
11341
11342 minimap->setDrawCallback([](const Widget& widget, SDL_Rect rect){
11343 int player = widget.getOwner();
11344 if ( ::minimapFrame && !::minimapFrame->isInvisible() )
11345 {
11346 drawMinimap(0, rect, true); // use the same texture
11347 }
11348 else
11349 {
11350 drawMinimap(player, rect, false);
11351 }
11352 if (!inputs.getVirtualMouse(player)->draw_cursor) {
11353 auto& cursor = minimap_cursor[player];
11354 auto image = Image::get("*#images/ui/MapAndLog/cursor.png");
11355 SDL_Rect pos;
11356 pos.x = cursor.x - image->getWidth() / 2 + rect.x;
11357 pos.y = cursor.y - image->getHeight() / 2 + rect.y;
11358 pos.w = image->getWidth();
11359 pos.h = image->getHeight();
11360 image->drawColor(nullptr, pos, SDL_Rect{0, 0, Frame::virtualScreenX, Frame::virtualScreenY}, 0xffffffff);
11361 }
11362 else
11363 {
11364 auto frame = ((Frame*)(widget.getParent()))->getParent();
11365 if ( Button* button = frame->findButton("close") )
11366 {
11367 if ( button->isHighlighted() )
11368 {
11369 players[player]->GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_MAP);
11370 if ( players[player]->GUI.activeModule != Player::GUI_t::MODULE_MAP )
11371 {
11372 players[player]->GUI.activateModule(Player::GUI_t::MODULE_MAP);
11373 }
11374 SDL_Rect pos = button->getAbsoluteSize();
11375 // make sure to adjust absolute size to camera viewport
11376 pos.x -= players[player]->camera_virtualx1();
11377 pos.y -= players[player]->camera_virtualy1();
11378 players[player]->hud.setCursorDisabled(false);
11379 players[player]->hud.updateCursorAnimation(pos.x - 1, pos.y - 1, pos.w, pos.h, inputs.getVirtualMouse(player)->draw_cursor);
11380 }
11381 }
11382 }
11383 });
11384
11385 minimap->setTickCallback([](Widget& widget){
11386 int player = widget.getOwner();
11387 auto& input = Input::inputs[player];
11388 auto minimap = static_cast<Frame*>(&widget);
11389
11390 auto frame = ((Frame*)(widget.getParent()))->getParent();
11391 if ( Frame::image_t* closeGlyph = frame->findImage("close glyph") )
11392 {
11393 closeGlyph->disabled = true;
11394 if ( inputs.hasController(player) && !inputs.getVirtualMouse(player)->draw_cursor )
11395 {
11396 Button* closeBtn = frame->findButton("close");
11397 SDL_Rect closeBtnPos = closeBtn->getSize();
11398 closeBtn->setSize(closeBtnPos);
11399
11400 closeGlyph->path = Input::inputs[player].getGlyphPathForBinding("MinimapClose");
11401 if ( auto imgGet = Image::get(closeGlyph->path.c_str()) )
11402 {
11403 closeGlyph->pos.w = imgGet->getWidth();
11404 closeGlyph->pos.h = imgGet->getHeight();
11405 closeGlyph->disabled = false;
11406 }
11407 closeGlyph->pos.x = closeBtn->getSize().x + closeBtn->getSize().w / 2 - closeGlyph->pos.w / 2;
11408 if ( closeGlyph->pos.x % 2 == 1 )
11409 {
11410 ++closeGlyph->pos.x;
11411 }
11412 closeGlyph->pos.y = closeBtn->getSize().y + closeBtn->getSize().h - 4;
11413 }
11414 }
11415
11416 static ConsoleVariable<float> speed("/minimap_cursor_speed", 4.f);
11417
11418 // gamepad moves cursor with right stick
11419 auto& cursor = minimap_cursor[player];
11420 const real_t fpsScale = getFPSScale(60.0); // ported from 60Hz
11421 float leftright = (input.analog("MinimapRight") - input.analog("MinimapLeft"));
11422 float updown = (input.analog("MinimapDown") - input.analog("MinimapUp"));
11423 cursor.x += (leftright) * (*speed * fpsScale);
11424 cursor.y += (updown) * (*speed * fpsScale);
11425 cursor.x = std::min(std::max((real_t)0, cursor.x), (real_t)minimap->getSize().w);
11426 cursor.y = std::min(std::max((real_t)0, cursor.y), (real_t)minimap->getSize().h);
11427 input.consumeBindingsSharedWithBinding("MinimapRight");
11428 input.consumeBindingsSharedWithBinding("MinimapLeft");
11429 input.consumeBindingsSharedWithBinding("MinimapDown");
11430 input.consumeBindingsSharedWithBinding("MinimapUp");
11431 if (input.consumeBinaryToggle("MinimapClose")) {
11432 input.consumeBindingsSharedWithBinding("MinimapClose");
11433 if (players[player]->minimap.mapWindow) {
11434 players[player]->minimap.mapWindow->removeSelf();
11435 players[player]->minimap.mapWindow = nullptr;
11436 if (players[player]->gui_mode == GUI_MODE_NONE) {
11437 players[player]->shootmode = true;
11438 }
11439 else
11440 {
11441 players[player]->GUI.returnToPreviousActiveModule();
11442 }
11443 Player::soundCancel();
11444 }
11445 }
11446
11447 static ConsoleVariable<bool> minimapGimpEnabled("/minimap_gimp_enabled", true);
11448
11449 // minimap pings
11450 if (minimapPingGimpTimer[player] <= 0 || !*minimapGimpEnabled) {
11451 if (input.consumeBinaryToggle("MinimapPing")) {
11452 auto mouse_position = inputs.getVirtualMouse(player)->draw_cursor ?
11453 minimap->getRelativeMousePosition(false):
11454 SDL_Rect{(int)cursor.x, (int)cursor.y, minimap->getSize().w, minimap->getSize().h};
11455 messagePlayer(clientnum, MESSAGE_DEBUG, "[Minimap] Clicked %d %d %d %d",
11456 mouse_position.x, mouse_position.y, mouse_position.w, mouse_position.h);
11457 if (mouse_position.w > 0 && mouse_position.h > 0) {
11458 const int size = std::max((int)map.width, (int)map.height);
11459 const int xdiff = std::max(0, (int)map.height - (int)map.width) / 2;
11460 const int ydiff = std::max(0, (int)map.width - (int)map.height) / 2;
11461 const int x = (mouse_position.x * size) / mouse_position.w - xdiff;
11462 const int y = (mouse_position.y * size) / mouse_position.h - ydiff;
11463 if (x >= 0 && y >= 0 && x < map.width && y < map.height) {
11464 MinimapPing newPing(ticks, player, x, y);
11465 sendMinimapPing(player, newPing.x, newPing.y);
11466
11467 // can also issue move commands via minimap
11468 FollowerRadialMenu& followerMenu = FollowerMenu[player];
11469 if ( !followerMenu.menuToggleClick && followerMenu.selectMoveTo )
11470 {
11471 if ( followerMenu.optionSelected == ALLY_CMD_MOVETO_SELECT )
11472 {
11473 if (!players[player]->usingCommand() && players[player]->bControlEnabled) {
11474 createParticleFollowerCommand(newPing.x, newPing.y, 0, FOLLOWER_TARGET_PARTICLE, 0);
11475 followerMenu.optionSelected = ALLY_CMD_MOVETO_CONFIRM;
11476 followerMenu.selectMoveTo = false;
11477 followerMenu.moveToX = static_cast<int>(newPing.x);
11478 followerMenu.moveToY = static_cast<int>(newPing.y);
11479 }
11480 }
11481 }
11482 }
11483 }
11484 }
11485 } else { // if (minimapPingGimpTimer[player] <= 0)
11486 --minimapPingGimpTimer[player];
11487 }
11488 });
11489
11490 auto label = frame->addField("label", 64);
11491 label->setSize(SDL_Rect{16, 0, w - 40, 32});
11492 label->setHJustify(Field::justify_t::LEFT);
11493 label->setVJustify(Field::justify_t::CENTER);
11494 label->setFont(bigfont_outline);
11495 label->setText(Language::get(5966));
11496
11497 auto closeGlyph = frame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
11498 "", "close glyph");
11499 closeGlyph->disabled = true;
11500 closeGlyph->ontop = true;
11501
11502 auto close_button = frame->addButton("close");
11503 close_button->setSize(SDL_Rect{frame->getSize().w - 30, 4, 26, 26});
11504 close_button->setColor(makeColor(255, 255, 255, 255));
11505 close_button->setHighlightColor(makeColor(255, 255, 255, 255));
11506 close_button->setText("X");
11507 close_button->setFont(smallfont_outline);
11508 close_button->setHideGlyphs(true);
11509 close_button->setHideKeyboardGlyphs(true);
11510 close_button->setHideSelectors(true);
11511 close_button->setMenuConfirmControlType(0);
11512 close_button->setBackground("*#images/ui/Shop/Button_X_00.png");
11513 close_button->setBackgroundHighlighted("*#images/ui/Shop/Button_XHigh_00.png");
11514 close_button->setBackgroundActivated("*#images/ui/Shop/Button_XPress_00.png");
11515 close_button->setTextHighlightColor(makeColor(201, 162, 100, 255));
11516 close_button->setCallback([](Button& button){
11517 const int player = button.getOwner();
11518 players[player]->minimap.mapWindow = nullptr;
11519 auto parent = static_cast<Frame*>(button.getParent());
11520 parent->removeSelf();
11521 if (players[player]->gui_mode == GUI_MODE_NONE) {
11522 players[player]->shootmode = true;
11523 }
11524 Player::soundCancel();
11525 });
11526}
11527
11528static ConsoleCommand ccmd_log_clear("/log_clear", "Clears log history",
11529 [](int argc, const char** argv){
11530 list_FreeAll(&messages);
11531 });
11532
11533void addMessageToLogWindow(int player, string_t* string) {
11534 auto& frame = players[player]->messageZone.logWindow;
11535 if (!frame || !string) {
11536 return;
11537 }
11538
11539 const int w = frame->getSize().w;
11540 const int h = frame->getSize().h;
11541
11542 auto subframe = frame->findFrame("subframe"); assert(subframe)(static_cast<void> (0));
11543 auto subframe_size = subframe->getActualSize();
11544 int y = subframe_size.h;
11545 if ( y != 4 )
11546 {
11547 y = subframe_size.h + *cvar_log_lineheight_offset;
11548 }
11549
11550 static ConsoleVariable<bool> timestamp_messages("/log_timestamp", true);
11551
11552 char buf[1024];
11553 const Uint32 time = string->time / TICKS_PER_SECOND50;
11554 const Uint32 hour = time / 3600;
11555 const Uint32 min = (time / 60) % 60;
11556 const Uint32 sec = time % 60;
11557 const int result = *timestamp_messages ?
11558 snprintf(buf, sizeof(buf), "[%.2u:%.2u:%.2u] %s",
11559 hour, min, sec, string->data):
11560 snprintf(buf, sizeof(buf), "%s", string->data);
11561 const int size = std::min(std::max(0, (int)sizeof(buf)), result);
11562
11563 static ConsoleVariable<std::string> font("/log_font",
11564 "fonts/PixelMaz_monospace.ttf#32#2");
11565 static ConsoleVariable<std::string> compactfont("/log_font_compact",
11566 "fonts/pixel_maz.ttf#32#2");
11567
11568 const bool bCompactWidth = players[player]->bUseCompactGUIWidth();
11569 auto field = subframe->addField("field", size + 1);
11570 int text_h = 0;
11571 int text_w = 0;
11572 int textHeight = 0;
11573 if ( bCompactWidth )
11574 {
11575 field->setFont(compactfont->c_str());
11576 field->setTextColor(string->color);
11577 field->addWordToHighlight(0, makeColorRGB(166, 166, 166));
11578 field->setText(buf);
11579 field->setPaddingPerLine(*cvar_log_multiline_pady);
11580 if ( auto text = field->getTextObject() )
11581 {
11582 textHeight = (int)(std::max(*cvar_log_lineheight_min, (int)text->getHeight()) * (int)string->lines + 2);
11583 text_h = textHeight + *cvar_log_lineheight_offset;
11584 text_w = (int)text->getWidth();
11585 field->setSize(SDL_Rect{ 8, y, text_w, textHeight });
11586 }
11587 }
11588 else
11589 {
11590 field->setFont(font->c_str());
11591 field->setTextColor(string->color);
11592 field->addWordToHighlight(0, makeColorRGB(166, 166, 166));
11593 field->addWordToHighlight(1, makeColorRGB(166, 166, 166));
11594 field->addWordToHighlight(2, makeColorRGB(166, 166, 166));
11595 field->addWordToHighlight(3, makeColorRGB(166, 166, 166));
11596 field->setText(buf);
11597 field->setPaddingPerLine(*cvar_log_multiline_pady);
11598 if ( auto text = field->getTextObject() )
11599 {
11600 textHeight = (int)(std::max(*cvar_log_lineheight_min, (int)text->getHeight()) * (int)string->lines + 2);
11601 text_h = textHeight + *cvar_log_lineheight_offset;
11602 text_w = (int)text->getWidth();
11603 field->setSize(SDL_Rect{ 8, y, text_w, textHeight });
11604 }
11605 }
11606
11607 //(void)snprintf(buf, sizeof(buf), "[%.2u:%.2u:%.2u]", hour, min, sec);
11608 //field->setTooltip(buf);
11609
11610 const int new_w = std::max(subframe_size.w, text_w + 8);
11611
11612 y = std::max(y, 0);
11613 y += textHeight;
11614 if (subframe_size.y >= subframe_size.h - subframe->getSize().h) {
11615 // advance scroll because we're already at bottom
11616 const int limit = new_w > w ?
11617 y - subframe->getSize().h + 16:
11618 y - subframe->getSize().h;
11619 subframe->setActualSize(SDL_Rect{subframe_size.x,
11620 std::max(0, limit), new_w, y});
11621 } else {
11622 // retain scroll position because we're looking at past history
11623 subframe->setActualSize(SDL_Rect{
11624 subframe_size.x, subframe_size.y, new_w, y});
11625 }
11626}
11627
11628void openLogWindow(int player) {
11629 if ( !players[player]->messageZone.logParentFrame )
11630 {
11631 return;
11632 }
11633 auto& frame = players[player]->messageZone.logWindow;
11634 if (frame) {
11635 frame->removeSelf();
11636 frame = nullptr;
11637 if ( players[player]->gui_mode == GUI_MODE_NONE ) {
11638 players[player]->shootmode = true;
11639 }
11640 else
11641 {
11642 players[player]->GUI.returnToPreviousActiveModule();
11643 }
11644 Player::soundCancel();
11645 return;
11646 }
11647
11648 players[player]->GUI.previousModule = players[player]->GUI.activeModule;
11649 if ( players[player]->shootmode )
11650 {
11651 players[player]->openStatusScreen(GUI_MODE_NONE,
11652 INVENTORY_MODE_ITEM, Player::GUI_t::MODULE_LOG);
11653 }
11654 else
11655 {
11656 players[player]->openStatusScreen(GUI_MODE_INVENTORY,
11657 players[player]->inventory_mode, Player::GUI_t::MODULE_LOG); // Reset the GUI to the inventory.
11658 }
11659
11660 Player::soundActivate();
11661
11662 auto& otherWindow = players[player]->minimap.mapWindow;
11663 if (otherWindow) {
11664 otherWindow->removeSelf();
11665 otherWindow = nullptr;
11666 }
11667 Frame* parent = players[player]->messageZone.logParentFrame;
11668 const SDL_Rect size = parent->getSize();
11669 int w = std::max(Frame::virtualScreenX / 2, size.w - 432);
11670 int h = std::max(Frame::virtualScreenY / 2, size.h - 256);
11671 static ConsoleVariable<int> cvar_log_splitscreen_w("/log_splitscreen_wborder", 40);
11672 static ConsoleVariable<int> cvar_log_splitscreen_h("/log_splitscreen_hborder", 64);
11673 int yoffset = 32;
11674 if ( (players[player]->bUseCompactGUIHeight() && players[player]->bUseCompactGUIWidth())
11675 || players[player]->bUseCompactGUIWidth() )
11676 {
11677 w = size.w - *cvar_log_splitscreen_w;
11678 if ( !(players[player]->bUseCompactGUIWidth() && !players[player]->bUseCompactGUIHeight()) ) // 2p tall splitscreen
11679 {
11680 h = size.h - *cvar_log_splitscreen_h;
11681 }
11682 }
11683 else if ( players[player]->bUseCompactGUIHeight() )
11684 {
11685 // 2p wide
11686 w = size.w - 432;
11687 h = size.h - 8;
11688 yoffset = 0;
11689 }
11690
11691 frame = parent->addFrame("log_window");
11692 frame->setOwner(player);
11693 frame->setSize(SDL_Rect{(size.w - w) / 2, (size.h - h) / 2 - yoffset, w, h});
11694 frame->setBorderColor(makeColor(51, 33, 26, 255));
11695 frame->setColor(makeColor(logBgColor->x, logBgColor->y, logBgColor->z, logBgColor->w));
11696 //frame->setBorder(2);
11697 frame->setBorder(0);
11698 frame->setTickCallback([](Widget& widget){
11699 const int player = widget.getOwner();
11700 auto frame = static_cast<Frame*>(&widget);
11701 if (players[player]->shootmode) {
11702 players[player]->messageZone.logWindow = nullptr;
11703 frame->removeSelf();
11704 }
11705
11706 const int w = frame->getSize().w;
11707 const int h = frame->getSize().h;
11708
11709 const bool using_keyboard = Input::inputs[player].getPlayerControlType() ==
11710 Input::playerControlType_t::PLAYER_CONTROLLED_BY_KEYBOARD;
11711
11712 const bool pressed = (ticks % TICKS_PER_SECOND50) >= TICKS_PER_SECOND50 / 2;
11713
11714 auto help_left = frame->findField("help_left"); assert(help_left)(static_cast<void> (0));
11715 auto help_center = frame->findField("help_center"); assert(help_center)(static_cast<void> (0));
11716 auto help_right = frame->findField("help_right"); assert(help_right)(static_cast<void> (0));
11717 auto help_left_div = frame->findField("help_left_div"); assert(help_left_div)(static_cast<void> (0));
11718 auto help_center_div = frame->findField("help_center_div"); assert(help_center_div)(static_cast<void> (0));
11719 auto help_right_div = frame->findField("help_right_div"); assert(help_right_div)(static_cast<void> (0));
11720 help_left->setDisabled(false);
11721 help_center->setDisabled(false);
11722 help_right->setDisabled(false);
11723 help_left_div->setDisabled(false);
11724 help_center_div->setDisabled(false);
11725 help_right_div->setDisabled(false);
11726
11727 auto LogHome = frame->findImage("LogHome"); assert(LogHome)(static_cast<void> (0));
11728 LogHome->disabled = true;
11729 auto LogEnd = frame->findImage("LogEnd"); assert(LogEnd)(static_cast<void> (0));
11730 LogEnd->disabled = true;
11731 auto LogPageUp = frame->findImage("LogPageUp"); assert(LogPageUp)(static_cast<void> (0));
11732 LogPageUp->disabled = true;
11733 auto LogPageDown = frame->findImage("LogPageDown"); assert(LogPageDown)(static_cast<void> (0));
11734 LogPageDown->disabled = true;
11735 auto LogScrollUp = frame->findImage("LogScrollUp"); assert(LogScrollUp)(static_cast<void> (0));
11736 LogScrollUp->disabled = true;
11737 auto LogScrollDown = frame->findImage("LogScrollDown"); assert(LogScrollDown)(static_cast<void> (0));
11738 LogScrollDown->disabled = true;
11739
11740 const bool bCompactWidth = players[player]->bUseCompactGUIWidth();
11741 const bool twoPrompts = bCompactWidth || (players[player]->bUseCompactGUIHeight() && !players[player]->bUseCompactGUIWidth() && *MainMenu::clipped_splitscreen);
11742 struct TextAndGlyphs
11743 {
11744 Frame::image_t* img1;
11745 Frame::image_t* img2;
11746 Field* div;
11747 Field* text;
11748 };
11749 std::vector<TextAndGlyphs> textAndGlyphs;
11750 if ( twoPrompts )
11751 {
11752 help_left->setText(Language::get(4315));
11753 help_center->setDisabled(true);
11754 help_center_div->setDisabled(true);
11755 help_right->setText(Language::get(4316));
11756
11757 textAndGlyphs.push_back(TextAndGlyphs{ LogPageUp, LogPageDown, help_left_div, help_left});
11758 textAndGlyphs.push_back(TextAndGlyphs{ LogScrollUp, LogScrollDown, help_right_div, help_right });
11759 }
11760 else
11761 {
11762 help_left->setText(Language::get(4314));
11763 help_center->setText(Language::get(4315));
11764 help_right->setText(Language::get(4316));
11765
11766 textAndGlyphs.push_back(TextAndGlyphs{ LogHome, LogEnd, help_left_div, help_left });
11767 textAndGlyphs.push_back(TextAndGlyphs{ LogPageUp, LogPageDown, help_center_div, help_center });
11768 textAndGlyphs.push_back(TextAndGlyphs{ LogScrollUp, LogScrollDown, help_right_div, help_right });
11769 }
11770
11771 for ( auto& entry : textAndGlyphs )
11772 {
11773 entry.div->setDisabled(entry.text->isDisabled());
11774
11775 entry.img1->disabled = entry.text->isDisabled();
11776 if ( !entry.img1->disabled )
11777 {
11778 std::string path = Input::inputs[player].getGlyphPathForBinding(entry.img1->name.c_str(), pressed);
11779 if ( auto glyph = Image::get(path.c_str()) )
11780 {
11781 entry.img1->color = 0xffffffff;
11782 entry.img1->path = path;
11783 entry.img1->pos.w = (int)glyph->getWidth();
11784 entry.img1->pos.h = (int)glyph->getHeight();
11785 entry.img1->pos.y = (int)(h - 16 - entry.img1->pos.h / 2);
11786 }
11787 }
11788
11789 entry.img2->disabled = entry.text->isDisabled();
11790 if ( !entry.img2->disabled )
11791 {
11792 std::string path = Input::inputs[player].getGlyphPathForBinding(entry.img2->name.c_str(), pressed);
11793 if ( auto glyph = Image::get(path.c_str()) )
11794 {
11795 entry.img2->color = 0xffffffff;
11796 entry.img2->path = path;
11797 entry.img2->pos.w = (int)glyph->getWidth();
11798 entry.img2->pos.h = (int)glyph->getHeight();
11799 entry.img2->pos.y = (int)(h - 16 - entry.img2->pos.h / 2);
11800 }
11801 }
11802 if ( entry.text->isDisabled() )
11803 {
11804 continue;
11805 }
11806
11807 if ( entry.text == help_left )
11808 {
11809 entry.img1->pos.x = 8;
11810 SDL_Rect divPos = entry.div->getSize();
11811 divPos.x = entry.img1->pos.x + entry.img1->pos.w + 4;
11812 if ( auto textGet = entry.div->getTextObject() )
11813 {
11814 divPos.w = textGet->getWidth();
11815 }
11816 entry.div->setSize(divPos);
11817
11818 entry.img2->pos.x = divPos.x + divPos.w + 4;
11819 SDL_Rect textPos = entry.text->getSize();
11820 textPos.x = entry.img2->pos.x + entry.img2->pos.w + 8;
11821 entry.text->setSize(textPos);
11822 }
11823 else if ( entry.text == help_center )
11824 {
11825 int totalWidth = entry.img1->pos.w + entry.img2->pos.w;
11826 SDL_Rect divPos = entry.div->getSize();
11827 if ( auto textGet = entry.div->getTextObject() )
11828 {
11829 divPos.w = textGet->getWidth();
11830 }
11831 SDL_Rect textPos = entry.text->getSize();
11832 if ( auto textGet = entry.text->getTextObject() )
11833 {
11834 textPos.w = textGet->getWidth();
11835 }
11836 totalWidth += 4 + 4 + divPos.w + 8 + textPos.w;
11837 int startx = w / 2 - (totalWidth / 2);
11838 if ( startx % 2 == 1 ) { ++startx; } // even alignment
11839
11840 entry.img1->pos.x = startx;
11841 divPos.x = entry.img1->pos.x + entry.img1->pos.w + 4;
11842 entry.div->setSize(divPos);
11843
11844 entry.img2->pos.x = divPos.x + divPos.w + 4;
11845 textPos.x = entry.img2->pos.x + entry.img2->pos.w + 8;
11846 entry.text->setSize(textPos);
11847 }
11848 else if ( entry.text == help_right )
11849 {
11850 int totalWidth = entry.img1->pos.w + entry.img2->pos.w;
11851 SDL_Rect divPos = entry.div->getSize();
11852 if ( auto textGet = entry.div->getTextObject() )
11853 {
11854 divPos.w = textGet->getWidth();
11855 }
11856 SDL_Rect textPos = entry.text->getSize();
11857 if ( auto textGet = entry.text->getTextObject() )
11858 {
11859 textPos.w = textGet->getWidth();
11860 }
11861 totalWidth += 4 + 4 + divPos.w + 8 + textPos.w;
11862 int startx = w - 8 - totalWidth;
11863 if ( startx % 2 == 1 ) { --startx; } // even alignment
11864
11865 entry.img1->pos.x = startx;
11866 divPos.x = entry.img1->pos.x + entry.img1->pos.w + 4;
11867 entry.div->setSize(divPos);
11868
11869 entry.img2->pos.x = divPos.x + divPos.w + 4;
11870 textPos.x = entry.img2->pos.x + entry.img2->pos.w + 8;
11871 entry.text->setSize(textPos);
11872 }
11873 }
11874
11875 auto subframe = frame->findFrame("subframe"); assert(subframe)(static_cast<void> (0));
11876 auto subframe_size = subframe->getActualSize();
11877 /*auto fields = subframe->getFields();
11878 if ( fields.size() > 0 )
11879 {
11880 SDL_Rect fieldPos = fields[fields.size() - 1]->getSize();
11881 if ( fieldPos.y + fieldPos.h > subframe_size.h )
11882 {
11883 subframe_size.h += (fieldPos.y + fieldPos.h - subframe_size.h);
11884 subframe_size.y += (fieldPos.y + fieldPos.h - subframe_size.h);
11885 subframe->setActualSize(subframe_size);
11886 }
11887 }*/
11888
11889 if (Input::inputs[player].consumeBinaryToggle("LogHome")) {
11890 Input::inputs[player].consumeBindingsSharedWithBinding("LogHome");
11891 subframe_size.x = 0;
11892 subframe_size.y = 0;
11893 subframe->setActualSize(subframe_size);
11894 }
11895 if (Input::inputs[player].consumeBinaryToggle("LogEnd")) {
11896 Input::inputs[player].consumeBindingsSharedWithBinding("LogEnd");
11897 const int limit = subframe_size.w > w ?
11898 subframe_size.h - subframe->getSize().h + 16:
11899 subframe_size.h - subframe->getSize().h;
11900 subframe_size.x = 0;
11901 subframe_size.y = std::max(0, limit);
11902 subframe->setActualSize(subframe_size);
11903 }
11904 if (Input::inputs[player].consumeBinaryToggle("LogPageUp")) {
11905 Input::inputs[player].consumeBindingsSharedWithBinding("LogPageUp");
11906 subframe_size.y -= subframe->getSize().h;
11907 subframe_size.y = std::max(0, subframe_size.y);
11908 subframe->setActualSize(subframe_size);
11909 }
11910 if (Input::inputs[player].consumeBinaryToggle("LogPageDown")) {
11911 Input::inputs[player].consumeBindingsSharedWithBinding("LogPageDown");
11912 subframe_size.y += subframe->getSize().h;
11913 const int limit = subframe_size.w > w ?
11914 subframe_size.h - subframe->getSize().h + 16:
11915 subframe_size.h - subframe->getSize().h;
11916 subframe_size.y = std::min(std::max(0, limit), subframe_size.y);
11917 subframe->setActualSize(subframe_size);
11918 }
11919 if ( Input::inputs[player].consumeBinaryToggle("LogClose") ) {
11920 Input::inputs[player].consumeBindingsSharedWithBinding("LogClose");
11921 if ( players[player]->messageZone.logWindow ) {
11922 Player::soundCancel();
11923 players[player]->messageZone.logWindow->removeSelf();
11924 players[player]->messageZone.logWindow = nullptr;
11925 if ( players[player]->gui_mode == GUI_MODE_NONE ) {
11926 players[player]->shootmode = true;
11927 }
11928 else
11929 {
11930 players[player]->GUI.returnToPreviousActiveModule();
11931 }
11932 }
11933 }
11934
11935 if ( Frame::image_t* closeGlyph = frame->findImage("close glyph") )
11936 {
11937 closeGlyph->disabled = true;
11938 if ( inputs.hasController(player) && !inputs.getVirtualMouse(player)->draw_cursor )
11939 {
11940 Button* closeBtn = frame->findButton("close");
11941 if (closeBtn)
11942 {
11943 SDL_Rect closeBtnPos = closeBtn->getSize();
11944 closeBtn->setSize(closeBtnPos);
11945
11946 closeGlyph->path = Input::inputs[player].getGlyphPathForBinding("LogClose");
11947 if ( auto imgGet = Image::get(closeGlyph->path.c_str()) )
11948 {
11949 closeGlyph->pos.w = imgGet->getWidth();
11950 closeGlyph->pos.h = imgGet->getHeight();
11951 closeGlyph->disabled = false;
11952 }
11953 closeGlyph->pos.x = closeBtn->getSize().x + closeBtn->getSize().w / 2 - closeGlyph->pos.w / 2;
11954 if ( closeGlyph->pos.x % 2 == 1 )
11955 {
11956 ++closeGlyph->pos.x;
11957 }
11958 closeGlyph->pos.y = closeBtn->getSize().y + closeBtn->getSize().h - 4;
11959 }
11960 }
11961 }
11962 });
11963
11964 // frame images
11965 {
11966 frame->addImage(
11967 SDL_Rect{0, 0, 16, 32},
11968 0xffffffff,
11969 "*#images/ui/MapAndLog/Hover_TL00.png",
11970 "TL");
11971 frame->addImage(
11972 SDL_Rect{16, 0, w - 32, 32},
11973 0xffffffff,
11974 "*#images/ui/MapAndLog/Hover_T00.png",
11975 "T");
11976 frame->addImage(
11977 SDL_Rect{w - 16, 0, 16, 32},
11978 0xffffffff,
11979 "*#images/ui/MapAndLog/Hover_TR00.png",
11980 "TR");
11981 auto L = frame->addImage(
11982 SDL_Rect{0, 32, 4, h - 64},
11983 0xffffffff,
11984 "*#images/ui/MapAndLog/Hover_L00.png",
11985 "L");
11986 L->ontop = true;
11987 auto R = frame->addImage(
11988 SDL_Rect{w - 4, 32, 4, h - 64},
11989 0xffffffff,
11990 "*#images/ui/MapAndLog/Hover_R00.png",
11991 "R");
11992 R->ontop = true;
11993 frame->addImage(
11994 SDL_Rect{0, h - 32, 16, 32},
11995 0xffffffff,
11996 "*#images/ui/MapAndLog/Hover_BL01.png",
11997 "BL");
11998 frame->addImage(
11999 SDL_Rect{16, h - 32, w - 32, 32},
12000 0xffffffff,
12001 "*#images/ui/MapAndLog/Hover_B01.png",
12002 "B");
12003 frame->addImage(
12004 SDL_Rect{w - 16, h - 32, 16, 32},
12005 0xffffffff,
12006 "*#images/ui/MapAndLog/Hover_BR01.png",
12007 "BR");
12008 }
12009
12010 auto subframe = frame->addFrame("subframe");
12011 subframe->setScrollWithLeftControls(false);
12012 subframe->setSize(SDL_Rect{0, 32, w, h - 64});
12013 subframe->setActualSize(SDL_Rect{0, 0, w, 4});
12014 subframe->setBorderColor(makeColor(22, 24, 29, 255));
12015 subframe->setSliderColor(makeColor(44, 48, 58, 255));
12016 subframe->setColor(makeColor(0, 0, 0, 0));
12017 subframe->setScrollBarsEnabled(true);
12018 //subframe->setBorder(2);
12019 subframe->setBorder(0);
12020
12021 for (auto node = messages.first; node != nullptr; node = node->next) {
12022 auto string = (string_t*)node->element;
12023 if ( string->player == player )
12024 {
12025 addMessageToLogWindow(player, string);
12026 }
12027 }
12028
12029 auto label = frame->addField("label", 64);
12030 label->setSize(SDL_Rect{16, 0, w - 40, 32});
12031 label->setHJustify(Field::justify_t::LEFT);
12032 label->setVJustify(Field::justify_t::CENTER);
12033 label->setFont(bigfont_outline);
12034 label->setText(Language::get(5967));
12035
12036 auto closeGlyph = frame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
12037 "", "close glyph");
12038 closeGlyph->disabled = true;
12039 closeGlyph->ontop = true;
12040
12041 auto close_button = frame->addButton("close");
12042 close_button->setSize(SDL_Rect{frame->getSize().w - 30, 4, 26, 26});
12043 close_button->setColor(makeColor(255, 255, 255, 255));
12044 close_button->setHighlightColor(makeColor(255, 255, 255, 255));
12045 close_button->setText("X");
12046 close_button->setFont(smallfont_outline);
12047 close_button->setHideGlyphs(true);
12048 close_button->setHideKeyboardGlyphs(true);
12049 close_button->setHideSelectors(true);
12050 close_button->setMenuConfirmControlType(0);
12051 close_button->setBackground("*#images/ui/Shop/Button_X_00.png");
12052 close_button->setBackgroundHighlighted("*#images/ui/Shop/Button_XHigh_00.png");
12053 close_button->setBackgroundActivated("*#images/ui/Shop/Button_XPress_00.png");
12054 close_button->setTextHighlightColor(makeColor(201, 162, 100, 255));
12055 close_button->setCallback([](Button& button){
12056 const int player = button.getOwner();
12057 players[player]->messageZone.logWindow = nullptr;
12058 auto parent = static_cast<Frame*>(button.getParent());
12059 parent->removeSelf();
12060 if (players[player]->gui_mode == GUI_MODE_NONE) {
12061 players[player]->shootmode = true;
12062 }
12063 else
12064 {
12065 players[player]->GUI.returnToPreviousActiveModule();
12066 }
12067 Player::soundCancel();
12068 });
12069 close_button->setDrawCallback([](const Widget& widget, SDL_Rect rect)
12070 {
12071 Button* button = (Button*)(&widget);
12072 const int player = button->getOwner();
12073 if ( inputs.getVirtualMouse(player)->draw_cursor )
12074 {
12075 if ( button->isHighlighted() )
12076 {
12077 players[player]->GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_LOG);
12078 if ( players[player]->GUI.activeModule != Player::GUI_t::MODULE_LOG )
12079 {
12080 players[player]->GUI.activateModule(Player::GUI_t::MODULE_LOG);
12081 }
12082 SDL_Rect pos = button->getAbsoluteSize();
12083 // make sure to adjust absolute size to camera viewport
12084 pos.x -= players[player]->camera_virtualx1();
12085 pos.y -= players[player]->camera_virtualy1();
12086 players[player]->hud.setCursorDisabled(false);
12087 players[player]->hud.updateCursorAnimation(pos.x - 1, pos.y - 1, pos.w, pos.h, inputs.getVirtualMouse(player)->draw_cursor);
12088 }
12089 }
12090 });
12091
12092 auto help_left = frame->addField("help_left", 128);
12093 help_left->setSize(SDL_Rect{0, h - 32, w, 32});
12094 help_left->setHJustify(Field::justify_t::LEFT);
12095 help_left->setVJustify(Field::justify_t::CENTER);
12096 help_left->setFont(smallfont_outline);
12097 help_left->setDisabled(true);
12098
12099 auto help_left_div = frame->addField("help_left_div", 32);
12100 help_left_div->setSize(SDL_Rect{ 0, h - 32, w, 32 });
12101 help_left_div->setHJustify(Field::justify_t::LEFT);
12102 help_left_div->setVJustify(Field::justify_t::CENTER);
12103 help_left_div->setFont(smallfont_outline);
12104 help_left_div->setDisabled(true);
12105 help_left_div->setText("/");
12106
12107 frame->addImage(SDL_Rect{0,0,0,0}, 0,
12108 "images/system/white.png", "LogHome");
12109 frame->addImage(SDL_Rect{0,0,0,0}, 0,
12110 "images/system/white.png", "LogEnd");
12111
12112 auto help_center = frame->addField("help_center", 128);
12113 help_center->setSize(SDL_Rect{0, h - 32, w, 32});
12114 help_center->setHJustify(Field::justify_t::LEFT);
12115 help_center->setVJustify(Field::justify_t::CENTER);
12116 help_center->setFont(smallfont_outline);
12117 help_center->setDisabled(true);
12118
12119 auto help_center_div = frame->addField("help_center_div", 32);
12120 help_center_div->setSize(SDL_Rect{ 0, h - 32, w, 32 });
12121 help_center_div->setHJustify(Field::justify_t::LEFT);
12122 help_center_div->setVJustify(Field::justify_t::CENTER);
12123 help_center_div->setFont(smallfont_outline);
12124 help_center_div->setDisabled(true);
12125 help_center_div->setText("/");
12126
12127 frame->addImage(SDL_Rect{0,0,0,0}, 0,
12128 "images/system/white.png", "LogPageUp");
12129 frame->addImage(SDL_Rect{0,0,0,0}, 0,
12130 "images/system/white.png", "LogPageDown");
12131
12132 auto help_right = frame->addField("help_right", 128);
12133 help_right->setSize(SDL_Rect{0, h - 32, w - 4, 32});
12134 help_right->setHJustify(Field::justify_t::RIGHT);
12135 help_right->setVJustify(Field::justify_t::CENTER);
12136 help_right->setFont(smallfont_outline);
12137 help_right->setDisabled(true);
12138
12139 auto help_right_div = frame->addField("help_right_div", 32);
12140 help_right_div->setSize(SDL_Rect{ 0, h - 32, w, 32 });
12141 help_right_div->setHJustify(Field::justify_t::LEFT);
12142 help_right_div->setVJustify(Field::justify_t::CENTER);
12143 help_right_div->setFont(smallfont_outline);
12144 help_right_div->setDisabled(true);
12145 help_right_div->setText("/");
12146
12147 frame->addImage(SDL_Rect{0,0,0,0}, 0,
12148 "images/system/white.png", "LogScrollUp");
12149 frame->addImage(SDL_Rect{0,0,0,0}, 0,
12150 "images/system/white.png", "LogScrollDown");
12151}
12152
12153std::map<std::string, std::pair<std::string, std::string>> Player::CharacterSheet_t::mapDisplayNamesDescriptions;
12154std::string Player::CharacterSheet_t::defaultString = "";
12155std::map<std::string, std::string> Player::CharacterSheet_t::hoverTextStrings;
12156void Player::CharacterSheet_t::loadCharacterSheetJSON()
12157{
12158 if ( !PHYSFS_getRealDir("/data/charsheet.json") )
12159 {
12160 printlog("[JSON]: Error: Could not find file: data/charsheet.json");
12161 }
12162 else
12163 {
12164 std::string inputPath = PHYSFS_getRealDir("/data/charsheet.json");
12165 inputPath.append("/data/charsheet.json");
12166
12167 File* fp = FileIO::open(inputPath.c_str(), "rb");
12168 if ( !fp )
12169 {
12170 printlog("[JSON]: Error: Could not open json file %s", inputPath.c_str());
12171 }
12172 else
12173 {
12174 char buf[65536];
12175 int count = fp->read(buf, sizeof(buf[0]), sizeof(buf));
12176 buf[count] = '\0';
12177 rapidjson::StringStream is(buf);
12178 FileIO::close(fp);
12179 rapidjson::Document d;
12180 d.ParseStream(is);
12181 if ( !d.HasMember("version") )
12182 {
12183 printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str());
12184 }
12185 else
12186 {
12187 if ( d.HasMember("level_strings") )
12188 {
12189 mapDisplayNamesDescriptions.clear();
12190 for ( rapidjson::Value::ConstMemberIterator itr = d["level_strings"].MemberBegin();
12191 itr != d["level_strings"].MemberEnd(); ++itr )
12192 {
12193 std::string name = "";
12194 std::string desc = "";
12195 if ( itr->value.HasMember("display_name") )
12196 {
12197 name = itr->value["display_name"].GetString();
12198 }
12199 if ( itr->value.HasMember("description") )
12200 {
12201 desc = itr->value["description"].GetString();
12202 }
12203 mapDisplayNamesDescriptions[itr->name.GetString()] = std::make_pair(name, desc);
12204 }
12205 }
12206 if ( d.HasMember("hover_text") )
12207 {
12208 hoverTextStrings.clear();
12209 for ( rapidjson::Value::ConstMemberIterator itr = d["hover_text"].MemberBegin();
12210 itr != d["hover_text"].MemberEnd(); ++itr )
12211 {
12212 if ( itr->value.IsObject() )
12213 {
12214 for ( rapidjson::Value::ConstMemberIterator inner_itr = itr->value.MemberBegin();
12215 inner_itr != itr->value.MemberEnd(); ++inner_itr )
12216 {
12217 hoverTextStrings[inner_itr->name.GetString()] = inner_itr->value.GetString();
12218 }
12219 }
12220 else
12221 {
12222 hoverTextStrings[itr->name.GetString()] = itr->value.GetString();
12223 }
12224 }
12225 }
12226 }
12227 }
12228 }
12229}
12230
12231const int NUM_CHARSHEET_TOOLTIP_BACKING_FRAMES = 9;
12232const int NUM_CHARSHEET_TOOLTIP_TEXT_FIELDS = 16;
12233std::map<int, Field*> characterSheetTooltipTextFields[MAXPLAYERS4];
12234std::map<int, Frame*> characterSheetTooltipTextBackingFrames[MAXPLAYERS4];
12235
12236static void charsheet_deselect_fn(Widget& widget) {
12237 if ( widget.isSelected()
12238 && players[widget.getOwner()]->GUI.activeModule != Player::GUI_t::MODULE_CHARACTERSHEET
12239 && !inputs.getVirtualMouse(widget.getOwner())->draw_cursor )
12240 {
12241 widget.deselect();
12242 }
12243};
12244
12245void Player::CharacterSheet_t::createCharacterSheet()
12246{
12247 char name[32];
12248 snprintf(name, sizeof(name), "player sheet %d", player.playernum);
12249 if ( !gameUIFrame[player.playernum]->findFrame(name) )
12250 {
12251 characterSheetTooltipTextFields[player.playernum].clear();
12252 characterSheetTooltipTextBackingFrames[player.playernum].clear();
12253
12254 Frame* sheetFrame = gameUIFrame[player.playernum]->addFrame(name);
12255 sheetFrame->setHollow(true);
12256 sheetFrame->setBorder(0);
12257 sheetFrame->setOwner(player.playernum);
12258 sheetFrame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(),
12259 players[player.playernum]->camera_virtualy1(),
12260 Frame::virtualScreenX,
12261 Frame::virtualScreenY });
12262 this->sheetFrame = sheetFrame;
12263
12264 const int bgWidth = 208;
12265 const int leftAlignX = sheetFrame->getSize().w - bgWidth;
12266 {
12267 Frame* fullscreenBg = sheetFrame->addFrame("sheet bg fullscreen");
12268 fullscreenBg->setSize(SDL_Rect{ leftAlignX,
12269 0, 208, Frame::virtualScreenY });
12270 // if splitscreen 3/4 - disable the fullscreen background + title text.
12271 fullscreenBg->addImage(SDL_Rect{ 0, 0, fullscreenBg->getSize().w, 360 },
12272 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_Window_01A_Top.png", "bg image");
12273
12274 const char* titleFont = "fonts/pixel_maz.ttf#32#2";
12275 auto characterSheetTitleText = fullscreenBg->addField("character sheet title text", 32);
12276 characterSheetTitleText->setFont(titleFont);
12277 characterSheetTitleText->setSize(SDL_Rect{ 6, 177, 202, 32 });
12278 characterSheetTitleText->setText(Language::get(5968));
12279 characterSheetTitleText->setVJustify(Field::justify_t::CENTER);
12280 characterSheetTitleText->setHJustify(Field::justify_t::CENTER);
12281 }
12282
12283 // log / map buttons
12284 {
12285 const char* buttonFont = "fonts/pixel_maz.ttf#32#2";
12286 SDL_Rect buttonFramePos{ leftAlignX + 9, 6, 196, 82 };
12287 auto buttonFrame = sheetFrame->addFrame("log map buttons");
12288 buttonFrame->setSize(buttonFramePos);
12289 buttonFrame->setDrawCallback([](const Widget& widget, SDL_Rect pos) {
12290 auto frame = (Frame*)(&widget);
12291 std::vector<const char*> buttons = {
12292 "map button",
12293 "log button"
12294 };
12295 for ( auto button : buttons )
12296 {
12297 if ( auto b = frame->findButton(button) )
12298 {
12299 if ( players[frame->getOwner()]->characterSheet.sheetDisplayType == CHARSHEET_DISPLAY_NORMAL )
12300 {
12301 b->setBackgroundHighlighted("*#images/ui/CharSheet/HUD_CharSheet_ButtonHigh_00.png");
12302 b->setBackground("*#images/ui/CharSheet/HUD_CharSheet_Button_00.png");
12303 b->setBackgroundActivated("*#images/ui/CharSheet/HUD_CharSheet_ButtonPress_00.png");
12304 }
12305 else
12306 {
12307 b->setBackgroundHighlighted("*#images/ui/CharSheet/HUD_CharSheet_ButtonHighCompact_00.png");
12308 b->setBackground("*#images/ui/CharSheet/HUD_CharSheet_ButtonCompact_00.png");
12309 b->setBackgroundActivated("*#images/ui/CharSheet/HUD_CharSheet_ButtonCompactPress_00.png");
12310 }
12311 }
12312 }
12313 });
12314
12315 SDL_Rect buttonPos{ 0, 0, buttonFramePos.w, 40 };
12316 auto mapButton = buttonFrame->addButton("map button");
12317 mapButton->setText(Language::get(4069));
12318 mapButton->setFont(buttonFont);
12319 mapButton->setBackground("*#images/ui/CharSheet/HUD_CharSheet_Button_00.png");
12320 mapButton->setSize(buttonPos);
12321 mapButton->setHideGlyphs(true);
12322 mapButton->setHideKeyboardGlyphs(true);
12323 mapButton->setHideSelectors(true);
12324 mapButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12325 mapButton->setColor(makeColor(255, 255, 255, 255));
12326 mapButton->setHighlightColor(makeColor(255, 255, 255, 255));
12327 mapButton->setCallback([](Button& button) {
12328 openMapWindow(button.getOwner());
12329 if ( players[button.getOwner()]->minimap.mapWindow ) {
12330 button.deselect();
12331 }
12332 });
12333 mapButton->setTickCallback(charsheet_deselect_fn);
12334
12335 auto mapSelector = buttonFrame->addFrame("map button selector");
12336 mapSelector->setSize(buttonPos);
12337 mapSelector->setHollow(true);
12338 //mapSelector->setClickable(true);
12339
12340 buttonPos.y = buttonPos.y + buttonPos.h + 2;
12341 auto logButton = buttonFrame->addButton("log button");
12342 logButton->setText(Language::get(4070));
12343 logButton->setFont(buttonFont);
12344 logButton->setBackground("*#images/ui/CharSheet/HUD_CharSheet_Button_00.png");
12345 logButton->setSize(buttonPos);
12346 logButton->setHideGlyphs(true);
12347 logButton->setHideKeyboardGlyphs(true);
12348 logButton->setHideSelectors(true);
12349 logButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12350 logButton->setColor(makeColor(255, 255, 255, 255));
12351 logButton->setHighlightColor(makeColor(255, 255, 255, 255));
12352 logButton->setCallback([](Button& button) {
12353 openLogWindow(button.getOwner());
12354 });
12355 logButton->setTickCallback(charsheet_deselect_fn);
12356
12357 auto logSelector = buttonFrame->addFrame("log button selector");
12358 logSelector->setSize(buttonPos);
12359 logSelector->setHollow(true);
12360 //logSelector->setClickable(true);
12361 }
12362
12363 // game timer
12364 {
12365 const char* timerFont = "fonts/pixel_maz.ttf#32#2";
12366 Uint32 timerTextColor = makeColor(188, 154, 114, 255);
12367
12368 Frame* timerFrame = sheetFrame->addFrame("game timer");
12369 timerFrame->setSize(SDL_Rect{ leftAlignX + 36, 90, 142, 26 });
12370 timerFrame->setDrawCallback([](const Widget& widget, SDL_Rect pos) {
12371 auto frame = (Frame*)(&widget);
12372 auto timerToggleImg = frame->findImage("timer icon img");
12373 auto timerSelector = frame->findButton("timer selector");
12374 if ( timerToggleImg && timerSelector )
12375 {
12376 if ( !players[frame->getOwner()]->characterSheet.showGameTimerAlways )
12377 {
12378 if ( timerSelector->isCurrentlyPressed() )
12379 {
12380 timerToggleImg->path = "images/ui/CharSheet/HUD_Button_Timer_PressOff00.png";
12381 }
12382 else if ( timerSelector->isHighlighted() || (!inputs.getVirtualMouse(widget.getOwner())->draw_cursor && timerSelector->isSelected()) )
12383 {
12384 timerToggleImg->path = "images/ui/CharSheet/HUD_Button_Timer_SelectOff00.png";
12385 }
12386 else
12387 {
12388 timerToggleImg->path = "images/ui/CharSheet/HUD_Button_Timer_UnselectOff00.png";
12389 }
12390 }
12391 else
12392 {
12393 if ( timerSelector->isCurrentlyPressed() )
12394 {
12395 timerToggleImg->path = "images/ui/CharSheet/HUD_Button_Timer_PressOn00.png";
12396 }
12397 else if ( timerSelector->isHighlighted() || (!inputs.getVirtualMouse(widget.getOwner())->draw_cursor && timerSelector->isSelected()) )
12398 {
12399 timerToggleImg->path = "images/ui/CharSheet/HUD_Button_Timer_SelectOn00.png";
12400 }
12401 else
12402 {
12403 timerToggleImg->path = "images/ui/CharSheet/HUD_Button_Timer_UnselectOn00.png";
12404 }
12405 }
12406 }
12407 });
12408 auto timerToggleImg = timerFrame->addImage(SDL_Rect{ 0, 0, 26, 26 }, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_Button_Timer_SelectOff00.png", "timer icon img");
12409 auto timerImg = timerFrame->addImage(SDL_Rect{ 30, 0, 112, 26 }, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_Timer_Backing_00.png", "timer bg img");
12410 auto timerText = timerFrame->addField("timer text", 32);
12411 timerText->setFont(timerFont);
12412
12413 auto timerButton = timerFrame->addButton("timer selector");
12414 timerButton->setSize(SDL_Rect{ 0, 0, timerToggleImg->pos.w + timerImg->pos.w, 26 });
12415 timerButton->setColor(makeColor(0, 0, 0, 0));
12416 timerButton->setHighlightColor(makeColor(0, 0, 0, 0));
12417 timerButton->setHideGlyphs(true);
12418 timerButton->setHideKeyboardGlyphs(true);
12419 timerButton->setHideSelectors(true);
12420 timerButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12421 timerButton->setCallback([](Button& button) {
12422 bool& bShowTimer = players[button.getOwner()]->characterSheet.showGameTimerAlways;
12423 bShowTimer = !bShowTimer;
12424 if ( bShowTimer )
12425 {
12426 Player::soundActivate();
12427 }
12428 else
12429 {
12430 Player::soundCancel();
12431 }
12432 });
12433 timerButton->setTickCallback(charsheet_deselect_fn);
12434
12435 SDL_Rect textPos = timerImg->pos;
12436 textPos.x += 12;
12437 timerText->setSize(textPos);
12438 timerText->setVJustify(Field::justify_t::CENTER);
12439 timerText->setText("00:92:30:89");
12440 timerText->setColor(timerTextColor);
12441 }
12442
12443 // skills button
12444 {
12445 const char* skillsFont = "fonts/pixel_maz.ttf#32#2";
12446 Frame* skillsButtonFrame = sheetFrame->addFrame("skills button frame");
12447 skillsButtonFrame->setSize(SDL_Rect{ leftAlignX + 14, 360 - 8 - 42, 186, 42 });
12448 auto skillsButton = skillsButtonFrame->addButton("skills button");
12449 skillsButton->setText(Language::get(4074));
12450 skillsButton->setFont(skillsFont);
12451 skillsButton->setBackground("*#images/ui/CharSheet/HUD_CharSheet_ButtonWide_00.png");
12452 skillsButton->setBackgroundActivated("*#images/ui/CharSheet/HUD_CharSheet_ButtonWidePress_00.png");
12453 skillsButton->setBackgroundHighlighted("*#images/ui/CharSheet/HUD_CharSheet_ButtonWideHigh_00.png");
12454 skillsButton->setSize(SDL_Rect{ 0, 0, skillsButtonFrame->getSize().w, skillsButtonFrame->getSize().h });
12455 skillsButton->setHideGlyphs(true);
12456 skillsButton->setHideKeyboardGlyphs(true);
12457 skillsButton->setHideSelectors(true);
12458 skillsButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12459 skillsButton->setColor(makeColor(255, 255, 255, 255));
12460 skillsButton->setHighlightColor(makeColor(255, 255, 255, 255));
12461 skillsButton->setCallback([](Button& button) {
12462 players[button.getOwner()]->skillSheet.openSkillSheet();
12463 });
12464 skillsButton->setTickCallback(charsheet_deselect_fn);
12465 }
12466
12467 // dungeon floor and level descriptor
12468 {
12469 const char* dungeonFont = "fonts/pixel_maz.ttf#32#2";
12470 Uint32 dungeonTextColor = makeColor(188, 154, 114, 255);
12471 Frame* dungeonFloorFrame = sheetFrame->addFrame("dungeon floor frame");
12472
12473 dungeonFloorFrame->setSize(SDL_Rect{ leftAlignX + 6, 118, 202, 52 });
12474 auto dungeonButton = dungeonFloorFrame->addButton("dungeon floor selector");
12475 dungeonButton->setSize(SDL_Rect{ 6, 6, 190, 44 });
12476 dungeonButton->setColor(makeColor(0, 0, 0, 0));
12477 dungeonButton->setHighlightColor(makeColor(0, 0, 0, 0));
12478 dungeonButton->setHideGlyphs(true);
12479 dungeonButton->setHideKeyboardGlyphs(true);
12480 dungeonButton->setHideSelectors(true);
12481 dungeonButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12482 dungeonButton->setTickCallback(charsheet_deselect_fn);
12483
12484 auto floorNameText = dungeonFloorFrame->addField("dungeon name text", 32);
12485 floorNameText->setFont(dungeonFont);
12486 floorNameText->setSize(SDL_Rect{ 10, 0, 182, 26 });
12487 floorNameText->setText("");
12488 floorNameText->setVJustify(Field::justify_t::CENTER);
12489 floorNameText->setHJustify(Field::justify_t::CENTER);
12490 floorNameText->setColor(dungeonTextColor);
12491
12492 auto floorLevelText = dungeonFloorFrame->addField("dungeon level text", 32);
12493 floorLevelText->setFont(dungeonFont);
12494 floorLevelText->setSize(SDL_Rect{ 10, 26, 182, 26 });
12495 floorLevelText->setText("");
12496 floorLevelText->setVJustify(Field::justify_t::CENTER);
12497 floorLevelText->setHJustify(Field::justify_t::CENTER);
12498 floorLevelText->setColor(dungeonTextColor);
12499 }
12500
12501 Frame* characterFrame = sheetFrame->addFrame("character info");
12502 const char* infoFont = "fonts/pixel_maz.ttf#32#2";
12503 characterFrame->setSize(SDL_Rect{ leftAlignX, 206, bgWidth, 116 });
12504 characterFrame->setHollow(true);
12505 Uint32 infoTextColor = makeColor(188, 154, 114, 255);
12506 Uint32 classTextColor = makeColor(74, 66, 207, 255);
12507
12508 sheetFrame->addImage(SDL_Rect{ characterFrame->getSize().x, characterFrame->getSize().y - 60,
12509 214, 170 }, 0xFFFFFFFF,
12510 "*#images/ui/CharSheet/HUD_CharSheet_Window_01A_TopCompact.png", "character info compact img");
12511
12512 Frame* characterInnerFrame = characterFrame->addFrame("character info inner frame");
12513 characterInnerFrame->setSize(SDL_Rect{ 6, 0, 202, 104 });
12514 {
12515 //characterInnerFrame->addImage(SDL_Rect{ 0, 0, characterInnerFrame->getSize().w, characterInnerFrame->getSize().h }, 0xFFFFFFFF,
12516 // "*#images/ui/CharSheet/HUD_CharSheet_Window_01A_TopTmp.png", "character info tmp img");
12517
12518
12519 SDL_Rect characterTextPos{ 2, 0, 198, 24 };
12520 auto nameText = characterInnerFrame->addField("character name text", 32);
12521 nameText->setFont(infoFont);
12522 nameText->setSize(SDL_Rect{ characterTextPos.x,
12523 characterTextPos.y,
12524 characterTextPos.w,
12525 characterTextPos.h });
12526 nameText->setText("Slartibartfast");
12527 nameText->setVJustify(Field::justify_t::CENTER);
12528 nameText->setHJustify(Field::justify_t::CENTER);
12529 nameText->setColor(infoTextColor);
12530
12531 /*characterInnerFrame->addImage(SDL_Rect{ 2, 2, 198, 20 }, makeColor(255, 255, 255, 64),
12532 "images/system/white.png", "character name frame");*/
12533 //characterInnerFrame->addFrame("character name selector")->setSize(SDL_Rect{ 2, 2, 198, 20 });
12534
12535 characterTextPos.x = 8;
12536 characterTextPos.w = 190;
12537 characterTextPos.y = 52;
12538 characterTextPos.h = 26;
12539 auto levelText = characterInnerFrame->addField("character level text", 32);
12540 levelText->setFont(infoFont);
12541 levelText->setSize(SDL_Rect{ characterTextPos.x,
12542 characterTextPos.y,
12543 characterTextPos.w,
12544 characterTextPos.h });
12545 levelText->setText("");
12546 levelText->setVJustify(Field::justify_t::CENTER);
12547 levelText->setColor(infoTextColor);
12548
12549 /*characterInnerFrame->addImage(SDL_Rect{ 4, 54, 194, 22 }, makeColor(255, 255, 255, 64),
12550 "images/system/white.png", "character level frame");*/
12551 auto classButton = characterInnerFrame->addButton("character class selector");
12552 classButton->setSize(SDL_Rect{ 4, 54, 194, 22 });
12553 classButton->setColor(makeColor(0, 0, 0, 0));
12554 classButton->setHighlightColor(makeColor(0, 0, 0, 0));
12555 classButton->setHideGlyphs(true);
12556 classButton->setHideKeyboardGlyphs(true);
12557 classButton->setHideSelectors(true);
12558 classButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12559 classButton->setTickCallback(charsheet_deselect_fn);
12560
12561 characterTextPos.x = 8;
12562 characterTextPos.w = 190;
12563 characterTextPos.y = 52;
12564 characterTextPos.h = 26;
12565 auto classText = characterInnerFrame->addField("character class text", 32);
12566 classText->setFont(infoFont);
12567 classText->setSize(SDL_Rect{ characterTextPos.x,
12568 characterTextPos.y,
12569 characterTextPos.w,
12570 characterTextPos.h });
12571 classText->setText("");
12572 classText->setVJustify(Field::justify_t::CENTER);
12573 classText->setHJustify(Field::justify_t::LEFT);
12574 classText->setTextColor(classTextColor);
12575
12576 characterTextPos.x = 4;
12577 characterTextPos.w = 194;
12578 characterTextPos.y = 26;
12579 characterTextPos.h = 26;
12580 auto raceText = characterInnerFrame->addField("character race text", 32);
12581 raceText->setFont(infoFont);
12582 raceText->setSize(SDL_Rect{ characterTextPos.x,
12583 characterTextPos.y,
12584 characterTextPos.w,
12585 characterTextPos.h });
12586 raceText->setText("");
12587 raceText->setVJustify(Field::justify_t::CENTER);
12588 raceText->setHJustify(Field::justify_t::CENTER);
12589 raceText->setColor(infoTextColor);
12590
12591 auto sexImg = characterInnerFrame->addImage(
12592 SDL_Rect{ characterTextPos.x + characterTextPos.w - 40, characterTextPos.y - 4, 16, 28 }, 0xFFFFFFFF,
12593 "*#images/ui/CharSheet/HUD_CharSheet_Sex_M_01.png", "character sex img");
12594
12595 /*characterInnerFrame->addImage(SDL_Rect{ 4, 28, 194, 22 }, makeColor(255, 255, 255, 64),
12596 "images/system/white.png", "character race frame");*/
12597 auto raceButton = characterInnerFrame->addButton("character race selector");
12598 raceButton->setSize(SDL_Rect{ 4, 28, 194, 22 });
12599 raceButton->setColor(makeColor(0, 0, 0, 0));
12600 raceButton->setHighlightColor(makeColor(0, 0, 0, 0));
12601 raceButton->setHideGlyphs(true);
12602 raceButton->setHideKeyboardGlyphs(true);
12603 raceButton->setHideSelectors(true);
12604 raceButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12605 raceButton->setTickCallback(charsheet_deselect_fn);
12606
12607 characterTextPos.x = 4;
12608 characterTextPos.w = 194;
12609 characterTextPos.y = 78;
12610 characterTextPos.h = 26;
12611 auto goldTitleText = characterInnerFrame->addField("gold text title", 8);
12612 goldTitleText->setFont(infoFont);
12613 goldTitleText->setSize(SDL_Rect{ 48, characterTextPos.y, 52, characterTextPos.h });
12614 goldTitleText->setText(Language::get(5969));
12615 goldTitleText->setVJustify(Field::justify_t::CENTER);
12616 goldTitleText->setColor(infoTextColor);
12617
12618 auto goldText = characterInnerFrame->addField("gold text", 32);
12619 goldText->setFont(infoFont);
12620 goldText->setSize(SDL_Rect{ 92, characterTextPos.y, 88, characterTextPos.h });
12621 goldText->setText("0");
12622 goldText->setVJustify(Field::justify_t::CENTER);
12623 goldText->setHJustify(Field::justify_t::CENTER);
12624 goldText->setColor(infoTextColor);
12625
12626 auto goldImg = characterInnerFrame->addImage(SDL_Rect{ 24, characterTextPos.y - 4, 20, 28 },
12627 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_ButtonMoney_00.png", "gold img");
12628
12629 /*characterInnerFrame->addImage(SDL_Rect{ 24, 80, 156, 22 }, makeColor(255, 255, 255, 64),
12630 "images/system/white.png", "character gold frame");*/
12631 auto goldButton = characterInnerFrame->addButton("character gold selector");
12632 goldButton->setSize(SDL_Rect{ 22, 80, 158, 22 });
12633 goldButton->setColor(makeColor(0, 0, 0, 0));
12634 goldButton->setHighlightColor(makeColor(0, 0, 0, 0));
12635 goldButton->setHideGlyphs(true);
12636 goldButton->setHideKeyboardGlyphs(true);
12637 goldButton->setHideSelectors(true);
12638 goldButton->setMenuConfirmControlType(0);
12639 goldButton->setTickCallback(charsheet_deselect_fn);
12640 }
12641
12642 {
12643 Frame* statsFrame = sheetFrame->addFrame("stats");
12644 const int statsFrameHeight = 182;
12645 statsFrame->setSize(SDL_Rect{ leftAlignX, 344, bgWidth, statsFrameHeight });
12646 statsFrame->addImage(SDL_Rect{ 0, 0, statsFrame->getSize().w, statsFrame->getSize().h },
12647 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_Window_01B_BotA.png", "stats bg img");
12648
12649 Frame* statsInnerFrame = statsFrame->addFrame("stats inner frame");
12650 statsInnerFrame->setSize(SDL_Rect{ 0, 0, statsFrame->getSize().w, statsFrame->getSize().h });
12651
12652 SDL_Rect iconPos{ 20, 8, 24, 24 };
12653 const int headingLeftX = iconPos.x + iconPos.w + 4;
12654 const int baseStatLeftX = headingLeftX + 32;
12655 const int modifiedStatLeftX = baseStatLeftX + 64;
12656 SDL_Rect textPos{ headingLeftX, iconPos.y, 40, iconPos.h };
12657 Uint32 statTextColor = hudColors.characterSheetNeutral;
12658
12659 const char* statFont = "fonts/pixel_maz.ttf#32#2";
12660 textPos.y = iconPos.y + 1;
12661 statsInnerFrame->addImage(iconPos, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_STR_00.png", "str icon");
12662 {
12663 auto textBase = statsInnerFrame->addField("str text title", 8);
12664 textBase->setVJustify(Field::justify_t::CENTER);
12665 textBase->setFont(statFont);
12666 textPos.x = headingLeftX;
12667 textBase->setSize(textPos);
12668 textBase->setText(Language::get(5300));
12669 textBase->setColor(statTextColor);
12670 auto textStat = statsInnerFrame->addField("str text stat", 32);
12671 textStat->setVJustify(Field::justify_t::CENTER);
12672 textStat->setHJustify(Field::justify_t::CENTER);
12673 textStat->setFont(statFont);
12674 textPos.x = baseStatLeftX;
12675 textStat->setSize(textPos);
12676 textStat->setText("0");
12677 textStat->setColor(statTextColor);
12678 auto textStatModified = statsInnerFrame->addField("str text modified", 32);
12679 textStatModified->setVJustify(Field::justify_t::CENTER);
12680 textStatModified->setHJustify(Field::justify_t::CENTER);
12681 textStatModified->setFont(statFont);
12682 textPos.x = modifiedStatLeftX;
12683 textStatModified->setSize(textPos);
12684 textStatModified->setText("");
12685 textStatModified->setColor(statTextColor);
12686
12687 auto statButton = statsInnerFrame->addButton("str button");
12688 statButton->setSize(SDL_Rect{ 12, iconPos.y + 2, statsFrame->getSize().w - 34, iconPos.h - 2 });
12689 statButton->setColor(makeColor(0, 0, 0, 0));
12690 statButton->setHighlightColor(makeColor(0, 0, 0, 0));
12691 statButton->setHideGlyphs(true);
12692 statButton->setHideKeyboardGlyphs(true);
12693 statButton->setHideSelectors(true);
12694 statButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12695 statButton->setTickCallback(charsheet_deselect_fn);
12696 }
12697 const int rowSpacing = 4;
12698 iconPos.y += iconPos.h + rowSpacing;
12699 textPos.y = iconPos.y + 1;
12700 statsInnerFrame->addImage(iconPos, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_DEX_00.png", "dex icon");
12701 {
12702 auto textBase = statsInnerFrame->addField("dex text title", 8);
12703 textBase->setVJustify(Field::justify_t::CENTER);
12704 textBase->setFont(statFont);
12705 textPos.x = headingLeftX;
12706 textBase->setSize(textPos);
12707 textBase->setText(Language::get(5301));
12708 textBase->setColor(statTextColor);
12709 auto textStat = statsInnerFrame->addField("dex text stat", 32);
12710 textStat->setVJustify(Field::justify_t::CENTER);
12711 textStat->setHJustify(Field::justify_t::CENTER);
12712 textStat->setFont(statFont);
12713 textPos.x = baseStatLeftX;
12714 textStat->setSize(textPos);
12715 textStat->setText("0");
12716 textStat->setColor(statTextColor);
12717 auto textStatModified = statsInnerFrame->addField("dex text modified", 32);
12718 textStatModified->setVJustify(Field::justify_t::CENTER);
12719 textStatModified->setHJustify(Field::justify_t::CENTER);
12720 textStatModified->setFont(statFont);
12721 textPos.x = modifiedStatLeftX;
12722 textStatModified->setSize(textPos);
12723 textStatModified->setText("2");
12724 textStatModified->setColor(statTextColor);
12725
12726 auto statButton = statsInnerFrame->addButton("dex button");
12727 statButton->setSize(SDL_Rect{ 12, iconPos.y + 2, statsFrame->getSize().w - 34, iconPos.h - 2 });
12728 statButton->setColor(makeColor(0, 0, 0, 0));
12729 statButton->setHighlightColor(makeColor(0, 0, 0, 0));
12730 statButton->setHideGlyphs(true);
12731 statButton->setHideKeyboardGlyphs(true);
12732 statButton->setHideSelectors(true);
12733 statButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12734 statButton->setTickCallback(charsheet_deselect_fn);
12735 }
12736
12737 iconPos.y += iconPos.h + rowSpacing;
12738 textPos.y = iconPos.y + 1;
12739 statsInnerFrame->addImage(iconPos, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_CON_00.png", "con icon");
12740 {
12741 auto textBase = statsInnerFrame->addField("con text title", 8);
12742 textBase->setVJustify(Field::justify_t::CENTER);
12743 textBase->setFont(statFont);
12744 textPos.x = headingLeftX;
12745 textBase->setSize(textPos);
12746 textBase->setText(Language::get(5302));
12747 textBase->setColor(statTextColor);
12748 auto textStat = statsInnerFrame->addField("con text stat", 32);
12749 textStat->setVJustify(Field::justify_t::CENTER);
12750 textStat->setHJustify(Field::justify_t::CENTER);
12751 textStat->setFont(statFont);
12752 textPos.x = baseStatLeftX;
12753 textStat->setSize(textPos);
12754 textStat->setText("0");
12755 textStat->setColor(statTextColor);
12756 auto textStatModified = statsInnerFrame->addField("con text modified", 32);
12757 textStatModified->setVJustify(Field::justify_t::CENTER);
12758 textStatModified->setHJustify(Field::justify_t::CENTER);
12759 textStatModified->setFont(statFont);
12760 textPos.x = modifiedStatLeftX;
12761 textStatModified->setSize(textPos);
12762 textStatModified->setText("");
12763 textStatModified->setColor(statTextColor);
12764
12765 auto statButton = statsInnerFrame->addButton("con button");
12766 statButton->setSize(SDL_Rect{ 12, iconPos.y + 2, statsFrame->getSize().w - 34, iconPos.h - 2 });
12767 statButton->setColor(makeColor(0, 0, 0, 0));
12768 statButton->setHighlightColor(makeColor(0, 0, 0, 0));
12769 statButton->setHideGlyphs(true);
12770 statButton->setHideKeyboardGlyphs(true);
12771 statButton->setHideSelectors(true);
12772 statButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12773 statButton->setTickCallback(charsheet_deselect_fn);
12774 }
12775
12776 iconPos.y += iconPos.h + rowSpacing;
12777 textPos.y = iconPos.y + 1;
12778 statsInnerFrame->addImage(iconPos, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_INT_00.png", "int icon");
12779 {
12780 auto textBase = statsInnerFrame->addField("int text title", 8);
12781 textBase->setVJustify(Field::justify_t::CENTER);
12782 textBase->setFont(statFont);
12783 textPos.x = headingLeftX;
12784 textBase->setSize(textPos);
12785 textBase->setText(Language::get(5303));
12786 textBase->setColor(statTextColor);
12787 auto textStat = statsInnerFrame->addField("int text stat", 32);
12788 textStat->setVJustify(Field::justify_t::CENTER);
12789 textStat->setHJustify(Field::justify_t::CENTER);
12790 textStat->setFont(statFont);
12791 textPos.x = baseStatLeftX;
12792 textStat->setSize(textPos);
12793 textStat->setText("0");
12794 textStat->setColor(statTextColor);
12795 auto textStatModified = statsInnerFrame->addField("int text modified", 32);
12796 textStatModified->setVJustify(Field::justify_t::CENTER);
12797 textStatModified->setHJustify(Field::justify_t::CENTER);
12798 textStatModified->setFont(statFont);
12799 textPos.x = modifiedStatLeftX;
12800 textStatModified->setSize(textPos);
12801 textStatModified->setText("");
12802 textStatModified->setColor(statTextColor);
12803
12804 auto statButton = statsInnerFrame->addButton("int button");
12805 statButton->setSize(SDL_Rect{ 12, iconPos.y + 2, statsFrame->getSize().w - 34, iconPos.h - 2 });
12806 statButton->setColor(makeColor(0, 0, 0, 0));
12807 statButton->setHighlightColor(makeColor(0, 0, 0, 0));
12808 statButton->setHideGlyphs(true);
12809 statButton->setHideKeyboardGlyphs(true);
12810 statButton->setHideSelectors(true);
12811 statButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12812 statButton->setTickCallback(charsheet_deselect_fn);
12813 }
12814
12815 iconPos.y += iconPos.h + rowSpacing;
12816 textPos.y = iconPos.y + 1;
12817 statsInnerFrame->addImage(iconPos, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_PER_00.png", "per icon");
12818 {
12819 auto textBase = statsInnerFrame->addField("per text title", 8);
12820 textBase->setVJustify(Field::justify_t::CENTER);
12821 textBase->setFont(statFont);
12822 textPos.x = headingLeftX;
12823 textBase->setSize(textPos);
12824 textBase->setText(Language::get(5304));
12825 textBase->setColor(statTextColor);
12826 auto textStat = statsInnerFrame->addField("per text stat", 32);
12827 textStat->setVJustify(Field::justify_t::CENTER);
12828 textStat->setHJustify(Field::justify_t::CENTER);
12829 textStat->setFont(statFont);
12830 textPos.x = baseStatLeftX;
12831 textStat->setSize(textPos);
12832 textStat->setText("0");
12833 textStat->setColor(statTextColor);
12834 auto textStatModified = statsInnerFrame->addField("per text modified", 32);
12835 textStatModified->setVJustify(Field::justify_t::CENTER);
12836 textStatModified->setHJustify(Field::justify_t::CENTER);
12837 textStatModified->setFont(statFont);
12838 textPos.x = modifiedStatLeftX;
12839 textStatModified->setSize(textPos);
12840 textStatModified->setText("");
12841 textStatModified->setColor(statTextColor);
12842
12843 auto statButton = statsInnerFrame->addButton("per button");
12844 statButton->setSize(SDL_Rect{ 12, iconPos.y + 2, statsFrame->getSize().w - 34, iconPos.h - 2 });
12845 statButton->setColor(makeColor(0, 0, 0, 0));
12846 statButton->setHighlightColor(makeColor(0, 0, 0, 0));
12847 statButton->setHideGlyphs(true);
12848 statButton->setHideKeyboardGlyphs(true);
12849 statButton->setHideSelectors(true);
12850 statButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12851 statButton->setTickCallback(charsheet_deselect_fn);
12852 }
12853
12854 iconPos.y += iconPos.h + rowSpacing;
12855 textPos.y = iconPos.y + 1;
12856 statsInnerFrame->addImage(iconPos, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_CHA_00.png", "chr icon");
12857 {
12858 auto textBase = statsInnerFrame->addField("chr text title", 8);
12859 textBase->setVJustify(Field::justify_t::CENTER);
12860 textBase->setFont(statFont);
12861 textPos.x = headingLeftX;
12862 textBase->setSize(textPos);
12863 textBase->setText(Language::get(5305));
12864 textBase->setColor(statTextColor);
12865 auto textStat = statsInnerFrame->addField("chr text stat", 32);
12866 textStat->setVJustify(Field::justify_t::CENTER);
12867 textStat->setHJustify(Field::justify_t::CENTER);
12868 textStat->setFont(statFont);
12869 textPos.x = baseStatLeftX;
12870 textStat->setSize(textPos);
12871 textStat->setText("0");
12872 textStat->setColor(statTextColor);
12873 auto textStatModified = statsInnerFrame->addField("chr text modified", 32);
12874 textStatModified->setVJustify(Field::justify_t::CENTER);
12875 textStatModified->setHJustify(Field::justify_t::CENTER);
12876 textStatModified->setFont(statFont);
12877 textPos.x = modifiedStatLeftX;
12878 textStatModified->setSize(textPos);
12879 textStatModified->setText("");
12880 textStatModified->setColor(statTextColor);
12881
12882 auto statButton = statsInnerFrame->addButton("chr button");
12883 statButton->setSize(SDL_Rect{ 12, iconPos.y + 2, statsFrame->getSize().w - 34, iconPos.h - 2 });
12884 statButton->setColor(makeColor(0, 0, 0, 0));
12885 statButton->setHighlightColor(makeColor(0, 0, 0, 0));
12886 statButton->setHideGlyphs(true);
12887 statButton->setHideKeyboardGlyphs(true);
12888 statButton->setHideSelectors(true);
12889 statButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12890 statButton->setTickCallback(charsheet_deselect_fn);
12891 }
12892 }
12893
12894 {
12895 Frame* attributesFrame = sheetFrame->addFrame("attributes");
12896 attributesFrame->setSize(SDL_Rect{ leftAlignX, 550, bgWidth, 182 });
12897
12898 attributesFrame->addImage(SDL_Rect{ 0, 0, attributesFrame->getSize().w, attributesFrame->getSize().h },
12899 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_Window_01B_BotB.png", "attributes bg img");
12900
12901 Frame* attributesInnerFrame = attributesFrame->addFrame("attributes inner frame");
12902 attributesInnerFrame->setSize(SDL_Rect{ 0, 0, attributesFrame->getSize().w, attributesFrame->getSize().h });
12903
12904 SDL_Rect iconPos{ 20, 8, 24, 24 };
12905 const int headingLeftX = iconPos.x + iconPos.w + 4;
12906 const int baseStatLeftX = headingLeftX + 48;
12907 SDL_Rect textPos{ headingLeftX, iconPos.y, 80, iconPos.h };
12908 Uint32 statTextColor = hudColors.characterSheetNeutral;
12909
12910 const char* attributeFont = "fonts/pixel_maz.ttf#32#2";
12911 textPos.y = iconPos.y + 1;
12912 attributesInnerFrame->addImage(iconPos, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_ATT_00.png", "atk icon");
12913 {
12914 auto textBase = attributesInnerFrame->addField("atk text title", 8);
12915 textBase->setVJustify(Field::justify_t::CENTER);
12916 textBase->setFont(attributeFont);
12917 textPos.x = headingLeftX;
12918 textBase->setSize(textPos);
12919 textBase->setText(Language::get(6093));
12920 textBase->setColor(statTextColor);
12921 auto textStat = attributesInnerFrame->addField("atk text stat", 32);
12922 textStat->setVJustify(Field::justify_t::CENTER);
12923 textStat->setHJustify(Field::justify_t::CENTER);
12924 textStat->setFont(attributeFont);
12925 textPos.x = baseStatLeftX;
12926 textStat->setSize(textPos);
12927 textStat->setText("14");
12928 textStat->setColor(statTextColor);
12929
12930 auto attributeButton = attributesInnerFrame->addButton("atk button");
12931 attributeButton->setSize(SDL_Rect{ 12, iconPos.y + 2, attributesFrame->getSize().w - 34, iconPos.h - 2 });
12932 attributeButton->setColor(makeColor(0, 0, 0, 0));
12933 attributeButton->setHighlightColor(makeColor(0, 0, 0, 0));
12934 attributeButton->setHideGlyphs(true);
12935 attributeButton->setHideKeyboardGlyphs(true);
12936 attributeButton->setHideSelectors(true);
12937 attributeButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12938 attributeButton->setTickCallback(charsheet_deselect_fn);
12939 }
12940
12941 const int rowSpacing = 4;
12942 iconPos.y += iconPos.h + rowSpacing;
12943 textPos.y = iconPos.y + 1;
12944 attributesInnerFrame->addImage(iconPos, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_AC_00.png", "ac icon");
12945 {
12946 auto textBase = attributesInnerFrame->addField("ac text title", 8);
12947 textBase->setVJustify(Field::justify_t::CENTER);
12948 textBase->setFont(attributeFont);
12949 textPos.x = headingLeftX;
12950 textBase->setSize(textPos);
12951 textBase->setText(Language::get(6094));
12952 textBase->setColor(statTextColor);
12953 auto textStat = attributesInnerFrame->addField("ac text stat", 32);
12954 textStat->setVJustify(Field::justify_t::CENTER);
12955 textStat->setHJustify(Field::justify_t::CENTER);
12956 textStat->setFont(attributeFont);
12957 textPos.x = baseStatLeftX;
12958 textStat->setSize(textPos);
12959 textStat->setText("3");
12960 textStat->setColor(statTextColor);
12961
12962 auto attributeButton = attributesInnerFrame->addButton("ac button");
12963 attributeButton->setSize(SDL_Rect{ 12, iconPos.y + 2, attributesFrame->getSize().w - 34, iconPos.h - 2 });
12964 attributeButton->setColor(makeColor(0, 0, 0, 0));
12965 attributeButton->setHighlightColor(makeColor(0, 0, 0, 0));
12966 attributeButton->setHideGlyphs(true);
12967 attributeButton->setHideKeyboardGlyphs(true);
12968 attributeButton->setHideSelectors(true);
12969 attributeButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
12970 attributeButton->setTickCallback(charsheet_deselect_fn);
12971 }
12972
12973 iconPos.y += iconPos.h + rowSpacing;
12974 textPos.y = iconPos.y + 1;
12975 attributesInnerFrame->addImage(iconPos, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_SPWR_00.png", "pwr icon");
12976 {
12977 auto textBase = attributesInnerFrame->addField("pwr text title", 8);
12978 textBase->setVJustify(Field::justify_t::CENTER);
12979 textBase->setFont(attributeFont);
12980 textPos.x = headingLeftX;
12981 textBase->setSize(textPos);
12982 textBase->setText(Language::get(6095));
12983 textBase->setColor(statTextColor);
12984 auto textStat = attributesInnerFrame->addField("pwr text stat", 32);
12985 textStat->setVJustify(Field::justify_t::CENTER);
12986 textStat->setHJustify(Field::justify_t::CENTER);
12987 textStat->setFont(attributeFont);
12988 textPos.x = baseStatLeftX;
12989 textStat->setSize(textPos);
12990 textStat->setText("115%");
12991 textStat->setColor(statTextColor);
12992
12993 auto attributeButton = attributesInnerFrame->addButton("pow button");
12994 attributeButton->setSize(SDL_Rect{ 12, iconPos.y + 2, attributesFrame->getSize().w - 34, iconPos.h - 2 });
12995 attributeButton->setColor(makeColor(0, 0, 0, 0));
12996 attributeButton->setHighlightColor(makeColor(0, 0, 0, 0));
12997 attributeButton->setHideGlyphs(true);
12998 attributeButton->setHideKeyboardGlyphs(true);
12999 attributeButton->setHideSelectors(true);
13000 attributeButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
13001 attributeButton->setTickCallback(charsheet_deselect_fn);
13002 }
13003
13004 iconPos.y += iconPos.h + rowSpacing;
13005 textPos.y = iconPos.y + 1;
13006 attributesInnerFrame->addImage(iconPos, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_RES_00.png", "res icon");
13007 {
13008 auto textBase = attributesInnerFrame->addField("res text title", 8);
13009 textBase->setVJustify(Field::justify_t::CENTER);
13010 textBase->setFont(attributeFont);
13011 textPos.x = headingLeftX;
13012 textBase->setSize(textPos);
13013 textBase->setText(Language::get(6096));
13014 textBase->setColor(statTextColor);
13015 auto textStat = attributesInnerFrame->addField("res text stat", 32);
13016 textStat->setVJustify(Field::justify_t::CENTER);
13017 textStat->setHJustify(Field::justify_t::CENTER);
13018 textStat->setFont(attributeFont);
13019 textPos.x = baseStatLeftX;
13020 textStat->setSize(textPos);
13021 textStat->setText("100%");
13022 textStat->setColor(statTextColor);
13023
13024 auto attributeButton = attributesInnerFrame->addButton("res button");
13025 attributeButton->setSize(SDL_Rect{ 12, iconPos.y + 2, attributesFrame->getSize().w - 34, iconPos.h - 2 });
13026 attributeButton->setColor(makeColor(0, 0, 0, 0));
13027 attributeButton->setHighlightColor(makeColor(0, 0, 0, 0));
13028 attributeButton->setHideGlyphs(true);
13029 attributeButton->setHideKeyboardGlyphs(true);
13030 attributeButton->setHideSelectors(true);
13031 attributeButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
13032 attributeButton->setTickCallback(charsheet_deselect_fn);
13033 }
13034
13035 iconPos.y += iconPos.h + rowSpacing;
13036 textPos.y = iconPos.y + 1;
13037 attributesInnerFrame->addImage(iconPos, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_REGEN_00.png", "regen icon");
13038 {
13039 auto textBase = attributesInnerFrame->addField("regen text title", 8);
13040 textBase->setVJustify(Field::justify_t::CENTER);
13041 textBase->setFont(attributeFont);
13042 textPos.x = headingLeftX;
13043 textBase->setSize(textPos);
13044 textBase->setText(Language::get(6097));
13045 textBase->setColor(statTextColor);
13046
13047 auto textDiv = attributesInnerFrame->addField("regen text divider", 4);
13048 textDiv->setVJustify(Field::justify_t::CENTER);
13049 textDiv->setHJustify(Field::justify_t::CENTER);
13050 textDiv->setFont(attributeFont);
13051 textPos.x = baseStatLeftX + 0;
13052 textDiv->setSize(textPos);
13053 textDiv->setText(" ");
13054 textDiv->setColor(statTextColor);
13055
13056 SDL_Rect hpmpTextPos = textPos;
13057 const int middleX = (textPos.x + textPos.w / 2);
13058 auto textRegenHP = attributesInnerFrame->addField("regen text hp", 16);
13059 textRegenHP->setVJustify(Field::justify_t::CENTER);
13060 textRegenHP->setHJustify(Field::justify_t::RIGHT);
13061 textRegenHP->setFont(attributeFont);
13062 hpmpTextPos.x = middleX - 4 - hpmpTextPos.w;
13063 textRegenHP->setSize(hpmpTextPos);
13064 textRegenHP->setText("0.2");
13065 textRegenHP->setColor(statTextColor);
13066
13067 auto textRegenMP = attributesInnerFrame->addField("regen text mp", 16);
13068 textRegenMP->setVJustify(Field::justify_t::CENTER);
13069 textRegenMP->setHJustify(Field::justify_t::LEFT);
13070 textRegenMP->setFont(attributeFont);
13071 hpmpTextPos.x = middleX + 4;
13072 textRegenMP->setSize(hpmpTextPos);
13073 textRegenMP->setText("0.1");
13074 textRegenMP->setColor(statTextColor);
13075
13076 auto attributeButton = attributesInnerFrame->addButton("rgn button");
13077 const int fullWidth = attributesFrame->getSize().w - 34 + 12;
13078 attributeButton->setSize(SDL_Rect{ 12, iconPos.y + 2, attributesFrame->getSize().w - 34 - 50, iconPos.h - 2 });
13079 attributeButton->setColor(makeColor(0, 0, 0, 0));
13080 attributeButton->setHighlightColor(makeColor(0, 0, 0, 0));
13081 attributeButton->setHideGlyphs(true);
13082 attributeButton->setHideKeyboardGlyphs(true);
13083 attributeButton->setHideSelectors(true);
13084 attributeButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
13085 attributeButton->setTickCallback(charsheet_deselect_fn);
13086
13087 auto attributeButton2 = attributesInnerFrame->addButton("rgn mp button");
13088 attributeButton2->setSize(SDL_Rect{ attributeButton->getSize().x + attributeButton->getSize().w,
13089 iconPos.y + 2, fullWidth - (attributeButton->getSize().x + attributeButton->getSize().w), iconPos.h - 2 });
13090 attributeButton2->setColor(makeColor(0, 0, 0, 0));
13091 attributeButton2->setHighlightColor(makeColor(0, 0, 0, 0));
13092 attributeButton2->setHideGlyphs(true);
13093 attributeButton2->setHideKeyboardGlyphs(true);
13094 attributeButton2->setHideSelectors(true);
13095 attributeButton2->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
13096 attributeButton2->setTickCallback(charsheet_deselect_fn);
13097 }
13098
13099 iconPos.y += iconPos.h + rowSpacing;
13100 textPos.y = iconPos.y + 1;
13101 attributesInnerFrame->addImage(iconPos, 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_WGT_00.png", "weight icon");
13102 {
13103 auto textBase = attributesInnerFrame->addField("weight text title", 8);
13104 textBase->setVJustify(Field::justify_t::CENTER);
13105 textBase->setFont(attributeFont);
13106 textPos.x = headingLeftX;
13107 textBase->setSize(textPos);
13108 textBase->setText(Language::get(6098));
13109 textBase->setColor(statTextColor);
13110 auto textStat = attributesInnerFrame->addField("weight text stat", 32);
13111 textStat->setVJustify(Field::justify_t::CENTER);
13112 textStat->setHJustify(Field::justify_t::CENTER);
13113 textStat->setFont(attributeFont);
13114 textPos.x = baseStatLeftX;
13115 textStat->setSize(textPos);
13116 textStat->setText("120");
13117 textStat->setColor(statTextColor);
13118
13119 auto attributeButton = attributesInnerFrame->addButton("wgt button");
13120 attributeButton->setSize(SDL_Rect{ 12, iconPos.y + 2, attributesFrame->getSize().w - 34, iconPos.h - 2 });
13121 attributeButton->setColor(makeColor(0, 0, 0, 0));
13122 attributeButton->setHighlightColor(makeColor(0, 0, 0, 0));
13123 attributeButton->setHideGlyphs(true);
13124 attributeButton->setHideKeyboardGlyphs(true);
13125 attributeButton->setHideSelectors(true);
13126 attributeButton->setMenuConfirmControlType(Widget::MENU_CONFIRM_CONTROLLER);
13127 attributeButton->setTickCallback(charsheet_deselect_fn);
13128 }
13129 }
13130
13131 {
13132 auto tooltipFrame = sheetFrame->addFrame("sheet tooltip");
13133 tooltipFrame->setSize(SDL_Rect{ leftAlignX - 200, 0, 200, 200 });
13134 tooltipFrame->setHollow(true);
13135 tooltipFrame->setInheritParentFrameOpacity(false);
13136 tooltipFrame->setDisabled(true);
13137 Uint32 color = makeColor(255, 255, 255, 255);
13138 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13139 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TL_00.png", skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
13140 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13141 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TR_00.png", skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
13142 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13143 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_T_00.png", skillsheetEffectBackgroundImages[TOP].c_str());
13144 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13145 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_L_00.png", skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str());
13146 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13147 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_R_00.png", skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str());
13148 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13149 makeColor(22, 24, 29, 255), "images/system/white.png", skillsheetEffectBackgroundImages[MIDDLE].c_str());
13150 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13151 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_BL_00.png", skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str());
13152 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13153 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_BR_00.png", skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str());
13154 tooltipFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13155 color, "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_B_00.png", skillsheetEffectBackgroundImages[BOTTOM].c_str());
13156 imageSetWidthHeight9x9(tooltipFrame, skillsheetEffectBackgroundImages);
13157 imageResizeToContainer9x9(tooltipFrame, SDL_Rect{ 0, 0, 200, 200 }, skillsheetEffectBackgroundImages);
13158 auto txt = tooltipFrame->addField("tooltip text", 1024);
13159 auto txtRightAlignHint = tooltipFrame->addField("tooltip text right align hint", 128);
13160 const char* tooltipFont = "fonts/pixel_maz_multiline.ttf#16#2";
13161 txt->setFont(tooltipFont);
13162 txt->setColor(makeColor(188, 154, 114, 255));
13163 txtRightAlignHint->setFont(tooltipFont);
13164 txtRightAlignHint->setColor(hudColors.characterSheetFaintText);
13165 auto glyph1 = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "images/system/white.png", "glyph 1");
13166 glyph1->disabled = true;
13167 auto glyph2 = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "images/system/white.png", "glyph 2");
13168 glyph2->disabled = true;
13169 auto glyph3 = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "images/system/white.png", "glyph 3");
13170 glyph3->disabled = true;
13171 auto glyph4 = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "images/system/white.png", "glyph 4");
13172 glyph4->disabled = true;
13173
13174 for ( int i = 1; i <= NUM_CHARSHEET_TOOLTIP_TEXT_FIELDS; ++i )
13175 {
13176 char txtEntryFieldName[64] = "";
13177 snprintf(txtEntryFieldName, sizeof(txtEntryFieldName), "txt %d", i);
13178 auto txtEntry = tooltipFrame->addField(txtEntryFieldName, 1024);
13179 txtEntry->setFont(tooltipFont);
13180 txtEntry->setDisabled(true);
13181 txtEntry->setVJustify(Field::justify_t::CENTER);
13182 txtEntry->setColor(makeColor(188, 154, 114, 255));
13183 txtEntry->setOntop(true);
13184
13185 characterSheetTooltipTextFields[player.playernum][i] = txtEntry;
13186 }
13187
13188 auto div = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 1 },
13189 makeColor(49, 53, 61, 255),
13190 "images/system/white.png", "tooltip divider 1");
13191 div->disabled = true;
13192 div = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 1 },
13193 makeColor(49, 53, 61, 255),
13194 "images/system/white.png", "tooltip divider 2");
13195 div->disabled = true;
13196
13197
13198 for ( int i = 1; i <= NUM_CHARSHEET_TOOLTIP_BACKING_FRAMES; ++i )
13199 {
13200 char backingFrameName[64] = "";
13201 snprintf(backingFrameName, sizeof(backingFrameName), "txt value backing frame %d", i);
13202 auto txtValueBackingFrame = tooltipFrame->addFrame(backingFrameName);
13203 txtValueBackingFrame->setSize(SDL_Rect{ 0, 0, tooltipFrame->getSize().w, tooltipFrame->getSize().h });
13204 txtValueBackingFrame->setDisabled(true);
13205 Uint32 color = makeColor(51, 33, 26, 255);
13206 txtValueBackingFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13207 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_TL00.png", skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
13208 txtValueBackingFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13209 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_TR00.png", skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
13210 txtValueBackingFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13211 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_T00.png", skillsheetEffectBackgroundImages[TOP].c_str());
13212 txtValueBackingFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13213 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_L00.png", skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str());
13214 txtValueBackingFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13215 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_R00.png", skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str());
13216 txtValueBackingFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13217 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_M00.png", skillsheetEffectBackgroundImages[MIDDLE].c_str());
13218 txtValueBackingFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13219 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_BL00.png", skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str());
13220 txtValueBackingFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13221 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_BR00.png", skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str());
13222 txtValueBackingFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
13223 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_B00.png", skillsheetEffectBackgroundImages[BOTTOM].c_str());
13224 imageSetWidthHeight9x9(txtValueBackingFrame, skillsheetEffectBackgroundImages);
13225
13226 characterSheetTooltipTextBackingFrames[player.playernum][i] = txtValueBackingFrame;
13227 }
13228
13229 {
13230 auto raceTooltip = tooltipFrame->addFrame("sheet race tooltip");
13231 raceTooltip->setSize(SDL_Rect{ 0, 24, 324, 664 });
13232 raceTooltip->setDisabled(true);
13233 raceTooltip->setHollow(true);
13234 const auto font = smallfont_outline;
13235
13236 auto details_text = raceTooltip->addField("details", 1024);
13237 details_text->setFont(font);
13238 details_text->setSize(SDL_Rect{ 0, 0, 242, 300 });
13239 details_text->setColor(hudColors.characterSheetOffWhiteText);
13240
13241 auto details_text_right = raceTooltip->addField("details_right", 1024);
13242 details_text_right->setFont(font);
13243 details_text_right->setSize(SDL_Rect{ 121, 0, 121, 300 });
13244 details_text_right->setColor(hudColors.characterSheetOffWhiteText);
13245
13246 MainMenu::RaceDescriptions::update_details_text(*raceTooltip, stats[player.playernum]);
13247 }
13248
13249 {
13250 auto classTooltip = tooltipFrame->addFrame("sheet class tooltip");
13251 classTooltip->setDisabled(true);
13252 classTooltip->setHollow(true);
13253 auto statGrowths = classTooltip->addFrame("stat growths");
13254 // stats definitions
13255 const char* class_stats_text[] = {
13256 ItemTooltips.getItemStatShortName("STR").c_str(),
13257 ItemTooltips.getItemStatShortName("DEX").c_str(),
13258 ItemTooltips.getItemStatShortName("CON").c_str(),
13259 ItemTooltips.getItemStatShortName("INT").c_str(),
13260 ItemTooltips.getItemStatShortName("PER").c_str(),
13261 ItemTooltips.getItemStatShortName("CHR").c_str()
13262 };
13263 constexpr int num_class_stats = sizeof(class_stats_text) / sizeof(class_stats_text[0]);
13264 constexpr SDL_Rect bottom{ 0, 0, 236, 30 };
13265 constexpr int column = bottom.w / num_class_stats;
13266
13267 classTooltip->setSize(SDL_Rect{ 0, 0, bottom.w, bottom.h });
13268 statGrowths->setSize(bottom);
13269
13270 for ( int c = 0; c < num_class_stats; ++c )
13271 {
13272 char buf[16];
13273 snprintf(buf, sizeof(buf), "%d", c);
13274 auto class_stat = statGrowths->addField(buf, 16);
13275 class_stat->setSize(SDL_Rect{
13276 bottom.x + column * c, bottom.y, column, bottom.h });
13277 class_stat->setHJustify(Field::justify_t::CENTER);
13278 class_stat->setVJustify(Field::justify_t::TOP);
13279 class_stat->setFont(smallfont_outline);
13280 class_stat->setText(class_stats_text[c]);
13281 //class_stat->setTickCallback([](Widget& widget) {class_stat_fn(*static_cast<Field*>(&widget), widget.getOwner()); });
13282
13283 SDL_Rect imgPos = class_stat->getSize();
13284 imgPos.x += imgPos.w / 2;
13285 imgPos.w = 14;
13286 imgPos.x -= imgPos.w / 2;
13287 imgPos.h = 16;
13288 imgPos.y -= imgPos.h - 4;
13289
13290 char buf2[32];
13291 /*snprintf(buf2, sizeof(buf2), "stat img top %d", c);
13292 auto class_stat_img_top = statGrowths->addImage(imgPos, 0xFFFFFFFF,
13293 "*#images/ui/Main Menus/Play/PlayerCreation/ClassSelection/statgrowth_hi2.png", buf2);
13294 class_stat_img_top->disabled = true;*/
13295 snprintf(buf2, sizeof(buf2), "stat img bottom %d", c);
13296 imgPos.y = class_stat->getSize().y + 17;
13297 auto class_stat_img_bottom = statGrowths->addImage(imgPos, 0xFFFFFFFF,
13298 "*#images/ui/Main Menus/Play/PlayerCreation/ClassSelection/statgrowth_lo2.png", buf2);
13299 class_stat_img_bottom->disabled = true;
13300 }
13301 }
13302 }
13303 }
13304}
13305
13306void Player::GUIDropdown_t::activateSelection(const std::string& name, const int option)
13307{
13308 auto& dropdown = allDropDowns[name];
13309 if ( dropdown.options[option].action == "no_action" )
13310 {
13311 messagePlayer(player.playernum, MESSAGE_DEBUG, "[Dropdowns]: Warning, no action for %s : option %d", name.c_str(), option);
13312 }
13313 else
13314 {
13315 if ( dropdown.options[option].action == "gold_drop_10" )
13316 {
13317 char dropCommand[32] = "";
13318 snprintf(dropCommand, sizeof(dropCommand), "/dropgold %d %d", player.playernum, 10);
13319 consoleCommand(dropCommand);
13320 }
13321 else if ( dropdown.options[option].action == "gold_drop_100" )
13322 {
13323 char dropCommand[32] = "";
13324 snprintf(dropCommand, sizeof(dropCommand), "/dropgold %d %d", player.playernum, 100);
13325 consoleCommand(dropCommand);
13326 }
13327 else if ( dropdown.options[option].action == "gold_drop_1000" )
13328 {
13329 char dropCommand[32] = "";
13330 snprintf(dropCommand, sizeof(dropCommand), "/dropgold %d %d", player.playernum, 1000);
13331 consoleCommand(dropCommand);
13332 }
13333 else if ( dropdown.options[option].action == "gold_drop_all" )
13334 {
13335 char dropCommand[32] = "";
13336 snprintf(dropCommand, sizeof(dropCommand), "/dropgold %d %d", player.playernum, std::max(0, stats[player.playernum]->GOLD));
13337 consoleCommand(dropCommand);
13338 }
13339 else if ( dropdown.options[option].action == "view_status_effects" )
13340 {
13341 if ( /*StatusEffectQueue[player.playernum].effectQueue.size() > 0*/ true )
13342 {
13343 player.GUI.previousModule = players[player.playernum]->GUI.activeModule;
13344 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor )
13345 {
13346 player.GUI.activateModule(Player::GUI_t::MODULE_STATUS_EFFECTS);
13347 }
13348 else
13349 {
13350 player.GUI.activateModule(Player::GUI_t::MODULE_NONE);
13351 }
13352 StatusEffectQueue[player.playernum].selectedElement = 0;
13353 player.hud.statusFxFocusedWindowActive = true;
13354 }
13355 }
13356 else if ( dropdown.options[option].action == "view_portrait" )
13357 {
13358 if ( playerInventoryFrames[player.playernum].characterPreview )
13359 {
13360 player.GUI.previousModule = players[player.playernum]->GUI.activeModule;
13361 player.GUI.activateModule(Player::GUI_t::MODULE_PORTRAIT);
13362
13363 SDL_Rect size = playerInventoryFrames[player.playernum].characterPreview->getAbsoluteSize();
13364
13365 // make sure to adjust absolute size to camera viewport
13366 const int offsetX = 4;
13367 const int offsetY = 6;
13368 size.x += offsetX;
13369 size.y += offsetY;
13370 size.w -= offsetX * 2;
13371 size.h -= offsetY * 2;
13372 size.x -= player.camera_virtualx1();
13373 size.y -= player.camera_virtualy1();
13374
13375 player.hud.updateCursorAnimation(size.x - 1, size.y - 1,
13376 size.w, size.h, inputs.getVirtualMouse(player.playernum)->draw_cursor);
13377 player.paperDoll.portraitActiveToEdit = true;
13378 }
13379 }
13380 else if ( dropdown.options[option].action == "inventory_autosort" )
13381 {
13382 autosortInventory(player.playernum);
13383 //quickStackItems();
13384 //playSound(139, 64);
13385 }
13386 messagePlayer(player.playernum, MESSAGE_DEBUG, "[Dropdowns]: Executing action '%s' for [%s] : option %d",
13387 dropdown.options[option].action.c_str(), name.c_str(), option);
13388 }
13389}
13390
13391bool Player::GUIDropdown_t::getDropDownAlignRight(const std::string& name)
13392{
13393 bool invert = false;
13394 if ( name == "drop_gold" )
13395 {
13396 if ( player.characterSheet.panelJustify == Player::PANEL_JUSTIFY_RIGHT
13397 && (player.bUseCompactGUIHeight() || player.bUseCompactGUIWidth()) )
13398 {
13399 invert = true;
13400 }
13401 }
13402 else if ( name == "chest_interact" )
13403 {
13404 if ( player.inventoryUI.bCompactView )
13405 {
13406 if ( player.inventoryUI.inventoryPanelJustify != PanelJustify_t::PANEL_JUSTIFY_RIGHT )
13407 {
13408 invert = true;
13409 }
13410 }
13411 }
13412 else if ( name == "paper_doll" )
13413 {
13414 if ( player.inventoryUI.bCompactView )
13415 {
13416 if ( player.inventoryUI.paperDollPanelJustify == PanelJustify_t::PANEL_JUSTIFY_RIGHT )
13417 {
13418 invert = true;
13419 }
13420 }
13421 }
13422 else if ( name == "item_interact" || name == "spell_interact" )
13423 {
13424 if ( player.inventoryUI.bCompactView )
13425 {
13426 Item* item = uidToItem(dropDownItem);
13427 if ( item && player.paperDoll.isItemOnDoll(*item) )
13428 {
13429 if ( player.inventoryUI.paperDollPanelJustify == PanelJustify_t::PANEL_JUSTIFY_RIGHT )
13430 {
13431 invert = true;
13432 }
13433 }
13434 else if ( item && itemCategory(item) == SPELL_CAT )
13435 {
13436 if ( player.inventoryUI.spellPanel.panelJustify == PanelJustify_t::PANEL_JUSTIFY_RIGHT )
13437 {
13438 invert = true;
13439 }
13440 }
13441 else
13442 {
13443 // normal inventory items
13444 if ( player.inventoryUI.inventoryPanelJustify == PanelJustify_t::PANEL_JUSTIFY_RIGHT )
13445 {
13446 invert = true;
13447 }
13448 }
13449 }
13450 }
13451 return (invert ? !allDropDowns[name].alignRight : allDropDowns[name].alignRight);
13452}
13453
13454void Player::CharacterSheet_t::processCharacterSheet()
13455{
13456 if ( !sheetFrame )
13457 {
13458 createCharacterSheet();
13459 }
13460
13461 sheetFrame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(),
13462 players[player.playernum]->camera_virtualy1(),
13463 players[player.playernum]->camera_virtualWidth(),
13464 players[player.playernum]->camera_virtualHeight() });
13465
13466 if ( !stats[player.playernum] || !players[player.playernum]->isLocalPlayer() )
13467 {
13468 sheetFrame->setDisabled(true);
13469 queuedElement = SHEET_UNSELECTED;
13470 return;
13471 }
13472
13473 bool bCompactView = false;
13474 if ( (keystatus[SDLK_u] && enableDebugKeys) || player.bUseCompactGUIHeight() || player.bUseCompactGUIWidth() )
13475 {
13476 bCompactView = true;
13477 }
13478
13479 bool hideAndExit = false;
13480 if ( nohud || player.shootmode || !(player.gui_mode == GUI_MODE_INVENTORY || player.gui_mode == GUI_MODE_SHOP) )
13481 {
13482 hideAndExit = true;
13483 }
13484 else if ( bCompactView && player.hud.compactLayoutMode != Player::HUD_t::COMPACT_LAYOUT_CHARSHEET )
13485 {
13486 hideAndExit = true;
13487 }
13488
13489 sheetFrame->setDisabled(false);
13490
13491 if ( bCompactView )
13492 {
13493 // justify opposite of inventory
13494 panelJustify = (player.inventoryUI.inventoryPanelJustify == Player::PANEL_JUSTIFY_LEFT)
13495 ? Player::PANEL_JUSTIFY_RIGHT : Player::PANEL_JUSTIFY_LEFT;
13496 }
13497 else
13498 {
13499 panelJustify = Player::PANEL_JUSTIFY_RIGHT;
13500 }
13501
13502 if ( player.inventoryUI.slideOutPercent <= .0001 )
13503 {
13504 isInteractable = !hideAndExit;
13505 if ( isInteractable && queuedElement != SHEET_UNSELECTED )
13506 {
13507 const bool updateCursor = true;
13508 const bool usingMouse = false;
13509 selectElement(queuedElement, usingMouse, updateCursor);
13510 queuedElement = SHEET_UNSELECTED;
13511 }
13512 }
13513 else
13514 {
13515 isInteractable = false;
13516 }
13517
13518 // resize elements for splitscreen
13519 {
13520 auto characterInfoFrame = sheetFrame->findFrame("character info");
13521 const int bgWidth = 208;
13522 const int leftAlignX = sheetFrame->getSize().w - bgWidth + player.inventoryUI.slideOutPercent * player.inventoryUI.slideOutWidth;
13523 int compactAlignX1 = -player.inventoryUI.slideOutPercent * player.inventoryUI.slideOutWidth;
13524 int compactAlignX2 = sheetFrame->getSize().w + player.inventoryUI.slideOutPercent * player.inventoryUI.slideOutWidth;
13525 auto compactImgBg = sheetFrame->findImage("character info compact img");
13526 Frame* dungeonFloorFrame = sheetFrame->findFrame("dungeon floor frame");
13527 Frame* buttonFrame = sheetFrame->findFrame("log map buttons");
13528 Frame* fullscreenBg = sheetFrame->findFrame("sheet bg fullscreen");
13529 Frame* timerFrame = sheetFrame->findFrame("game timer");
13530 Frame* skillsButtonFrame = sheetFrame->findFrame("skills button frame");
13531 Frame* characterInnerFrame = characterInfoFrame->findFrame("character info inner frame");
13532
13533 auto statsFrame = sheetFrame->findFrame("stats");
13534 auto attributesFrame = sheetFrame->findFrame("attributes");
13535
13536 if ( bCompactView )
13537 {
13538 // compact view
13539 sheetDisplayType = CHARSHEET_DISPLAY_COMPACT;
13540
13541 compactImgBg->disabled = false;
13542 compactImgBg->pos.x = (panelJustify == PANEL_JUSTIFY_RIGHT ? compactAlignX1 : compactAlignX2 - compactImgBg->pos.w);
13543 compactImgBg->pos.y = 0;
13544
13545 SDL_Rect pos = characterInfoFrame->getSize();
13546 pos.x = (panelJustify == PANEL_JUSTIFY_RIGHT ? compactAlignX1 : compactAlignX2 - pos.w);
13547 pos.y = compactImgBg->pos.y + 60;
13548 characterInfoFrame->setSize(pos);
13549
13550 SDL_Rect dungeonFloorFramePos = dungeonFloorFrame->getSize();
13551 dungeonFloorFramePos.x = pos.x + (panelJustify == PANEL_JUSTIFY_RIGHT ? 6 : 0);
13552 dungeonFloorFramePos.y = pos.y - 56;
13553 dungeonFloorFrame->setSize(dungeonFloorFramePos);
13554
13555 SDL_Rect characterInnerFramePos = characterInnerFrame->getSize();
13556 characterInnerFramePos.x = (panelJustify == PANEL_JUSTIFY_RIGHT ? 6 : 0);
13557 characterInnerFrame->setSize(characterInnerFramePos);
13558
13559 SDL_Rect buttonFramePos = buttonFrame->getSize();
13560 buttonFramePos.w = 198;
13561 buttonFramePos.h = 40;
13562 buttonFramePos.x = (panelJustify == PANEL_JUSTIFY_RIGHT ? compactAlignX1 + 8 : compactAlignX2 - buttonFramePos.w - 8);
13563 buttonFramePos.y = compactImgBg->pos.y + compactImgBg->pos.h + 1;
13564 buttonFrame->setSize(buttonFramePos);
13565
13566 {
13567 SDL_Rect buttonPos{ 0, 1, 98, 38 };
13568 auto mapBtn = buttonFrame->findButton("map button");
13569 mapBtn->setText(Language::get(4071));
13570 mapBtn->setSize(buttonPos);
13571 auto mapSelector = buttonFrame->findFrame("map button selector");
13572 SDL_Rect mapSelectorPos = buttonPos;
13573 mapSelectorPos.x += 6;
13574 mapSelectorPos.w -= 12;
13575 mapSelectorPos.y += 4;
13576 mapSelectorPos.h -= 8;
13577 mapSelector->setSize(mapSelectorPos);
13578
13579 buttonPos.x = buttonFramePos.w - buttonPos.w;
13580 auto logBtn = buttonFrame->findButton("log button");
13581 logBtn->setText(Language::get(4072));
13582 logBtn->setSize(buttonPos);
13583 auto logSelector = buttonFrame->findFrame("log button selector");
13584 SDL_Rect logSelectorPos = buttonPos;
13585 logSelectorPos.x += 6;
13586 logSelectorPos.w -= 12;
13587 logSelectorPos.y += 4;
13588 logSelectorPos.h -= 10;
13589 logSelector->setSize(logSelectorPos);
13590 }
13591
13592 fullscreenBg->setDisabled(true);
13593
13594 auto statsPos = statsFrame->getSize();
13595 statsPos.x = (panelJustify == PANEL_JUSTIFY_RIGHT ? compactAlignX2 - statsPos.w : compactAlignX1);
13596 statsPos.y = 0;
13597 statsFrame->setSize(statsPos);
13598
13599 auto attributesPos = attributesFrame->getSize();
13600 attributesPos.x = (panelJustify == PANEL_JUSTIFY_RIGHT ? compactAlignX2 - attributesPos.w : compactAlignX1);
13601 attributesPos.y = statsPos.y + statsPos.h - 4; // 4 pixels above bottom edge of stats pane
13602 attributesFrame->setSize(attributesPos);
13603 }
13604 else
13605 {
13606 // standard view
13607 sheetDisplayType = CHARSHEET_DISPLAY_NORMAL;
13608
13609 SDL_Rect pos = characterInfoFrame->getSize();
13610 pos.x = leftAlignX;
13611 pos.y = 206;
13612 characterInfoFrame->setSize(pos);
13613
13614 SDL_Rect dungeonFloorFramePos = dungeonFloorFrame->getSize();
13615 dungeonFloorFramePos.x = pos.x + 6;
13616 dungeonFloorFramePos.y = pos.y - 88;
13617 dungeonFloorFrame->setSize(dungeonFloorFramePos);
13618
13619 SDL_Rect characterInnerFramePos = characterInnerFrame->getSize();
13620 characterInnerFramePos.x = (panelJustify == PANEL_JUSTIFY_RIGHT ? 6 : 0);
13621 characterInnerFrame->setSize(characterInnerFramePos);
13622
13623 SDL_Rect buttonFramePos = buttonFrame->getSize();
13624 buttonFramePos.x = leftAlignX + 9;
13625 buttonFramePos.y = 6;
13626 buttonFramePos.w = 196;
13627 buttonFramePos.h = 82;
13628 buttonFrame->setSize(buttonFramePos);
13629 {
13630 SDL_Rect buttonPos{ 0, 0, buttonFramePos.w, 40 };
13631 auto mapBtn = buttonFrame->findButton("map button");
13632 mapBtn->setText(Language::get(4069));
13633 mapBtn->setSize(buttonPos);
13634 auto mapSelector = buttonFrame->findFrame("map button selector");
13635 mapSelector->setSize(buttonPos);
13636
13637 buttonPos.y = buttonPos.y + buttonPos.h + 2;
13638 auto logBtn = buttonFrame->findButton("log button");
13639 logBtn->setText(Language::get(4070));
13640 logBtn->setSize(buttonPos);
13641 auto logSelector = buttonFrame->findFrame("log button selector");
13642 logSelector->setSize(buttonPos);
13643 }
13644
13645 SDL_Rect timerFramePos = timerFrame->getSize();
13646 timerFramePos.x = leftAlignX + 36;
13647 timerFrame->setSize(timerFramePos);
13648
13649 SDL_Rect skillsButtonFramePos = skillsButtonFrame->getSize();
13650 skillsButtonFramePos.x = leftAlignX + 14;
13651 skillsButtonFrame->setSize(skillsButtonFramePos);
13652
13653 fullscreenBg->setDisabled(false);
13654 SDL_Rect fullscreenBgPos = fullscreenBg->getSize();
13655 fullscreenBgPos.x = sheetFrame->getSize().w - fullscreenBgPos.w + player.inventoryUI.slideOutPercent * player.inventoryUI.slideOutWidth;
13656 fullscreenBg->setSize(fullscreenBgPos);
13657 compactImgBg->disabled = true;
13658
13659 Frame::image_t* fullscreenBgImg = fullscreenBg->findImage("bg image");
13660
13661 auto statsPos = statsFrame->getSize();
13662 statsPos.x = sheetFrame->getSize().w - statsPos.w + player.inventoryUI.slideOutPercent * player.inventoryUI.slideOutWidth;
13663 statsPos.y = fullscreenBgImg->pos.y + fullscreenBgImg->pos.h;
13664 statsFrame->setSize(statsPos);
13665
13666 auto attributesPos = attributesFrame->getSize();
13667 attributesPos.x = sheetFrame->getSize().w - attributesPos.w + player.inventoryUI.slideOutPercent * player.inventoryUI.slideOutWidth;
13668 attributesPos.y = statsPos.y + statsPos.h - 4; // 4 px overlap attributes pane
13669 attributesFrame->setSize(attributesPos);
13670
13671 }
13672 }
13673
13674 updateCharacterSheetTooltip(SHEET_UNSELECTED, SDL_Rect{ 0, 0, 0, 0 }); // to reset the tooltip from displaying.
13675 if ( hideAndExit )
13676 {
13677 queuedElement = SHEET_UNSELECTED;
13678 sheetFrame->setDisabled(true);
13679 return;
13680 }
13681
13682 player.GUI.handleCharacterSheetMovement();
13683
13684 {
13685 auto characterInfoFrame = sheetFrame->findFrame("character info");
13686 assert(characterInfoFrame)(static_cast<void> (0));
13687 auto characterInnerFrame = characterInfoFrame->findFrame("character info inner frame");
13688 assert(characterInnerFrame)(static_cast<void> (0));
13689 auto statsFrame = sheetFrame->findFrame("stats");
13690 assert(statsFrame)(static_cast<void> (0));
13691 auto statsInnerFrame = statsFrame->findFrame("stats inner frame");
13692 assert(statsInnerFrame)(static_cast<void> (0));
13693 auto attributesFrame = sheetFrame->findFrame("attributes");
13694 assert(attributesFrame)(static_cast<void> (0));
13695 auto attributesInnerFrame = attributesFrame->findFrame("attributes inner frame");
13696 assert(attributesInnerFrame)(static_cast<void> (0));
13697 SheetElements targetElement = SHEET_ENUM_END;
13698 auto logButton = sheetFrame->findFrame("log map buttons")->findButton("log button");
13699 auto mapButton = sheetFrame->findFrame("log map buttons")->findButton("map button");
13700 auto skillsButton = sheetFrame->findFrame("skills button frame")->findButton("skills button");
13701 Button* strButton = statsInnerFrame->findButton("str button");
13702 Button* dexButton = statsInnerFrame->findButton("dex button");
13703 Button* conButton = statsInnerFrame->findButton("con button");
13704 Button* intButton = statsInnerFrame->findButton("int button");
13705 Button* perButton = statsInnerFrame->findButton("per button");
13706 Button* chrButton = statsInnerFrame->findButton("chr button");
13707 Button* atkButton = attributesInnerFrame->findButton("atk button");
13708 Button* acButton = attributesInnerFrame->findButton("ac button");
13709 Button* powButton = attributesInnerFrame->findButton("pow button");
13710 Button* resButton = attributesInnerFrame->findButton("res button");
13711 Button* rgnButton = attributesInnerFrame->findButton("rgn button");
13712 Button* rgnMpButton = attributesInnerFrame->findButton("rgn mp button");
13713 Button* wgtButton = attributesInnerFrame->findButton("wgt button");
13714
13715 auto sheetTimer = sheetFrame->findFrame("game timer");
13716 if ( bCompactView )
13717 {
13718 sheetTimer->setDisabled(true);
13719 }
13720 else
13721 {
13722 sheetTimer->setDisabled(false);
13723 }
13724
13725 bool mouseHighlightActive = inputs.getVirtualMouse(player.playernum)->draw_cursor
13726 && (!player.GUI.isDropdownActive())
13727 && isInteractable;
13728
13729 if ( player.bUseCompactGUIWidth()
13730 && (player.minimap.mapWindow || player.messageZone.logWindow) )
13731 {
13732 mouseHighlightActive = false;
13733 }
13734
13735 if ( mouseHighlightActive )
13736 {
13737 if ( skillsButton->isHighlighted() )
13738 {
13739 targetElement = SHEET_SKILL_LIST;
13740 }
13741 else if ( logButton->isHighlighted() )
13742 {
13743 targetElement = SHEET_OPEN_LOG;
13744 }
13745 else if ( mapButton->isHighlighted() )
13746 {
13747 targetElement = SHEET_OPEN_MAP;
13748 }
13749 else if ( !sheetTimer->isDisabled() && sheetTimer->findButton("timer selector")->isHighlighted() )
13750 {
13751 targetElement = SHEET_TIMER;
13752 }
13753 else if ( sheetFrame->findFrame("dungeon floor frame")->findButton("dungeon floor selector")->isHighlighted() )
13754 {
13755 targetElement = SHEET_DUNGEON_FLOOR;
13756 }
13757 else if ( characterInnerFrame->findButton("character class selector")->isHighlighted() )
13758 {
13759 targetElement = SHEET_CHAR_CLASS;
13760 }
13761 else if ( characterInnerFrame->findButton("character race selector")->isHighlighted() )
13762 {
13763 targetElement = SHEET_CHAR_RACE_SEX;
13764 }
13765 else if ( characterInnerFrame->findButton("character gold selector")->isHighlighted() )
13766 {
13767 targetElement = SHEET_GOLD;
13768 }
13769 else if ( strButton->isHighlighted() )
13770 {
13771 targetElement = SHEET_STR;
13772 }
13773 else if ( dexButton->isHighlighted() )
13774 {
13775 targetElement = SHEET_DEX;
13776 }
13777 else if ( conButton->isHighlighted() )
13778 {
13779 targetElement = SHEET_CON;
13780 }
13781 else if ( intButton->isHighlighted() )
13782 {
13783 targetElement = SHEET_INT;
13784 }
13785 else if ( perButton->isHighlighted() )
13786 {
13787 targetElement = SHEET_PER;
13788 }
13789 else if ( chrButton->isHighlighted() )
13790 {
13791 targetElement = SHEET_CHR;
13792 }
13793 else if ( atkButton->isHighlighted() )
13794 {
13795 targetElement = SHEET_ATK;
13796 }
13797 else if ( acButton->isHighlighted() )
13798 {
13799 targetElement = SHEET_AC;
13800 }
13801 else if ( powButton->isHighlighted() )
13802 {
13803 targetElement = SHEET_POW;
13804 }
13805 else if ( resButton->isHighlighted() )
13806 {
13807 targetElement = SHEET_RES;
13808 }
13809 else if ( rgnButton->isHighlighted() )
13810 {
13811 targetElement = SHEET_RGN;
13812 }
13813 else if ( rgnMpButton->isHighlighted() )
13814 {
13815 targetElement = SHEET_RGN_MP;
13816 }
13817 else if ( wgtButton->isHighlighted() )
13818 {
13819 targetElement = SHEET_WGT;
13820 }
13821 }
13822 if ( targetElement != SHEET_ENUM_END )
13823 {
13824 player.GUI.activateModule(Player::GUI_t::MODULE_CHARACTERSHEET);
13825 selectElement(targetElement, true, true);
13826 }
13827 else
13828 {
13829 if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
13830 {
13831 if ( player.GUI.activeModule == Player::GUI_t::MODULE_CHARACTERSHEET
13832 && !player.GUI.isDropdownActive() && !player.GUI.dropdownMenu.bClosedThisTick )
13833 {
13834 // no moused over objects, deactivate the cursor.
13835 player.GUI.activateModule(Player::GUI_t::MODULE_NONE);
13836 }
13837 }
13838 }
13839 //messagePlayer(0, "%d", player.GUI.activeModule);
13840 // players[player.playernum]->GUI.warpControllerToModule(false); - use this for controller input
13841 }
13842
13843 updateGameTimer();
13844 updateStats();
13845 updateAttributes();
13846 updateCharacterInfo();
13847}
13848
13849std::map<std::string, Player::GUIDropdown_t::DropDown_t> Player::GUIDropdown_t::allDropDowns;
13850
13851bool Player::GUIDropdown_t::set(const std::string name)
13852{
13853 if ( allDropDowns.find(name) == allDropDowns.end() )
13854 {
13855 printlog("[Dropdown]: Could not find dropdown name %s", name.c_str());
13856 return false;
13857 }
13858
13859 if ( currentName != name )
13860 {
13861 create(name);
13862 }
13863 currentName = name;
13864 return true;
13865}
13866
13867void Player::GUIDropdown_t::process()
13868{
13869 bClosedThisTick = false;
13870 if ( dropdownBlockClickFrame )
13871 {
13872 dropdownBlockClickFrame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(),
13873 players[player.playernum]->camera_virtualy1(),
13874 players[player.playernum]->camera_virtualWidth(),
13875 players[player.playernum]->camera_virtualHeight() });
13876 }
13877 if ( !dropdownFrame ) { return; }
13878
13879 if ( !bOpen )
13880 {
13881 dropdownFrame->setDisabled(true);
13882 dropDownOptionSelected = -1;
13883 dropDownItem = 0;
13884 dropDownX = 0;
13885 dropDownY = 0;
13886 dropDownToggleClick = false;
13887 return;
13888 }
13889 else
13890 {
13891 dropdownFrame->setDisabled(false);
13892 }
13893
13894 auto highlightImageMid = dropdownFrame->findImage("interact selected highlight mid");
13895 highlightImageMid->disabled = true;
13896 highlightImageMid->color = hudColors.itemContextMenuOptionSelectedImg;
13897 auto highlightImageLeft = dropdownFrame->findImage("interact selected highlight left");
13898 highlightImageLeft->disabled = true;
13899 highlightImageLeft->color = hudColors.itemContextMenuOptionSelectedImg;
13900 auto highlightImageRight = dropdownFrame->findImage("interact selected highlight right");
13901 highlightImageRight->disabled = true;
13902 highlightImageRight->color = hudColors.itemContextMenuOptionSelectedImg;
13903
13904 size_t index = 0;
13905 unsigned int maxWidth = 0;
13906 if ( auto interactText = dropdownFrame->findField("interact text") )
13907 {
13908 interactText->setColor(hudColors.itemContextMenuHeadingText);
13909 if ( auto textGet = Text::get(interactText->getText(), interactText->getFont(),
13910 interactText->getTextColor(), interactText->getOutlineColor()) )
13911 {
13912 maxWidth = textGet->getWidth();
13913 }
13914 }
13915 int maxHeight = 0;
13916 const int textPaddingX = 8;
13917
13918 auto dropDown = allDropDowns[currentName];
13919 std::vector<ItemContextMenuPrompts> contextOptions;
13920 Item* item = uidToItem(dropDownItem);
13921 if ( currentName == "chest_interact" )
13922 {
13923 list_t* chest_inventory = nullptr;
13924 if ( openedChest[player.playernum] )
13925 {
13926 if ( multiplayer == CLIENT2 )
13927 {
13928 chest_inventory = &chestInv[player.playernum];
13929 }
13930 else if ( openedChest[player.playernum]->children.first && openedChest[player.playernum]->children.first->element )
13931 {
13932 chest_inventory = (list_t*)openedChest[player.playernum]->children.first->element;
13933 }
13934 }
13935
13936 if ( chest_inventory )
13937 {
13938 for ( node_t* node = chest_inventory->first; node != NULL__null; node = node->next )
13939 {
13940 Item* chestItem = (Item*)node->element;
13941 if ( !chestItem )
13942 {
13943 continue;
13944 }
13945 if ( chestItem->uid == dropDownItem )
13946 {
13947 item = chestItem;
13948 break;
13949 }
13950 }
13951 }
13952 }
13953
13954 if ( dropDownItem != 0 && !item )
13955 {
13956 close();
13957 return;
13958 }
13959 if ( currentName == "item_interact" || currentName == "hotbar_interact"
13960 || currentName == "chest_interact"
13961 || currentName == "spell_interact" )
13962 {
13963 if ( item )
13964 {
13965 dropDown.options.clear();
13966 contextOptions = getContextMenuOptionsForItem(player.playernum, item);
13967 if ( currentName == "hotbar_interact" )
13968 {
13969 contextOptions.push_back(PROMPT_CLEAR_HOTBAR_SLOT);
13970 if ( itemCategory(item) == SPELLBOOK )
13971 {
13972 for ( auto it = contextOptions.begin(); it != contextOptions.end(); ++it )
13973 {
13974 if ( (*it) == PROMPT_INTERACT )
13975 {
13976 (*it) = PROMPT_INTERACT_SPELLBOOK_HOTBAR;
13977 }
13978 }
13979 }
13980 }
13981 if ( player.inventoryUI.useItemDropdownOnGamepad == Inventory_t::GAMEPAD_DROPDOWN_FULL )
13982 {
13983 for ( auto it = contextOptions.begin(); it != contextOptions.end(); )
13984 {
13985 if ( (*it) == PROMPT_APPRAISE )
13986 {
13987 it = contextOptions.erase(it);
13988 continue;
13989 }
13990 ++it;
13991 }
13992 }
13993 else if ( player.inventoryUI.useItemDropdownOnGamepad == Inventory_t::GAMEPAD_DROPDOWN_COMPACT )
13994 {
13995 for ( auto it = contextOptions.begin(); it != contextOptions.end(); )
13996 {
13997 if ( (*it) == PROMPT_APPRAISE || (*it) == PROMPT_DROP )
13998 {
13999 it = contextOptions.erase(it);
14000 continue;
14001 }
14002 ++it;
14003 }
14004 }
14005 for ( auto option : contextOptions )
14006 {
14007 dropDown.options.push_back(DropdownOption_t(getContextMenuLangEntry(player.playernum, option, *item), "", "", ""));
14008 }
14009 }
14010 }
14011 if ( dropDown.module != Player::GUI_t::MODULE_NONE )
14012 {
14013 if ( dropDown.module != player.GUI.activeModule )
14014 {
14015 close();
14016 return;
14017 }
14018 }
14019
14020 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor )
14021 {
14022 bool pressedDown = Input::inputs[player.playernum].consumeBinaryToggle("InventoryMoveDown");
14023 pressedDown = Input::inputs[player.playernum].consumeBinaryToggle("InventoryMoveDownAnalog") || pressedDown;
14024
14025 bool pressedUp = Input::inputs[player.playernum].consumeBinaryToggle("InventoryMoveUp");
14026 pressedUp = Input::inputs[player.playernum].consumeBinaryToggle("InventoryMoveUpAnalog") || pressedUp;
14027
14028 if ( pressedDown && player.bControlEnabled && !gamePaused && !player.usingCommand() )
14029 {
14030 ++dropDownOptionSelected;
14031 if ( dropDownOptionSelected >= dropDown.options.size() )
14032 {
14033 dropDownOptionSelected = 0;
14034 }
14035 Player::soundMovement();
14036 }
14037 else if ( pressedUp && player.bControlEnabled && !gamePaused && !player.usingCommand() )
14038 {
14039 --dropDownOptionSelected;
14040 if ( dropDownOptionSelected < 0 )
14041 {
14042 dropDownOptionSelected = dropDown.options.size() - 1;
14043 }
14044 Player::soundMovement();
14045 }
14046 }
14047
14048 std::vector<std::tuple<Frame::image_t*, Field*, Frame::image_t*>> optionFrames;
14049 index = 1;
14050 for ( auto& option : dropDown.options )
14051 {
14052 char glyphname[32] = "";
14053 snprintf(glyphname, sizeof(glyphname), "glyph %d", (int)index);
14054 char optionname[32] = "";
14055 snprintf(optionname, sizeof(optionname), "interact option %d", (int)index);
14056
14057 auto img = dropdownFrame->findImage(glyphname);
14058 auto txt = dropdownFrame->findField(optionname);
14059 optionFrames.push_back(std::make_tuple(img, txt, nullptr));
14060 txt->setColor(hudColors.itemContextMenuOptionText);
14061 //txt->setColor(makeColor(148, 82, 3, 255));
14062 txt->setDisabled(false);
14063 img->disabled = true;
14064
14065 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor )
14066 {
14067 if ( dropDownOptionSelected + 1 == index )
14068 {
14069 img->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuConfirm");
14070 if ( auto imgGet = Image::get(img->path.c_str()) )
14071 {
14072 img->pos.w = imgGet->getWidth();
14073 img->pos.h = imgGet->getHeight();
14074 img->disabled = false;
14075 }
14076 }
14077 }
14078 /*if ( option.controllerGlyph != "" && inputs.getVirtualMouse(player.playernum)->lastMovementFromController )
14079 {
14080 img->path = Input::inputs[player.playernum].getGlyphPathForBinding(option.controllerGlyph.c_str());
14081 img->disabled = false;
14082 }
14083 else if ( option.keyboardGlyph != "" && inputs.getVirtualMouse(player.playernum)->draw_cursor )
14084 {
14085 img->path = Input::inputs[player.playernum].getGlyphPathForBinding(option.keyboardGlyph.c_str());
14086 img->disabled = false;
14087 }*/
14088
14089 txt->setText(option.text.c_str());
14090 if ( auto textGet = Text::get(txt->getText(), txt->getFont(),
14091 txt->getTextColor(), txt->getOutlineColor()) )
14092 {
14093 maxWidth = std::max(textGet->getWidth() + 40, maxWidth);
14094
14095 SDL_Rect size = txt->getSize();
14096 size.w = textGet->getWidth();
14097 if ( img->disabled || true )
14098 {
14099 size.x = textPaddingX;
14100 txt->setHJustify(Field::justify_t::CENTER);
14101 }
14102 else
14103 {
14104 size.x = img->pos.x + img->pos.w + textPaddingX;
14105 txt->setHJustify(Field::justify_t::LEFT);
14106 }
14107 txt->setSize(size);
14108 }
14109
14110 img->pos.x = txt->getSize().x - 8;
14111 img->pos.y = txt->getSize().y + txt->getSize().h / 2 - img->pos.h / 2;
14112
14113 maxHeight = std::max(std::max(txt->getSize().y + txt->getSize().h, img->pos.y + img->pos.h), maxHeight);
14114 ++index;
14115 }
14116
14117 const int rightClickProtectBuffer = (right_click_protect ? 0 : 10);
14118 const Sint32 mousex = (inputs.getMouse(player.playernum, Inputs::X) / (float)xres) * (float)Frame::virtualScreenX;
14119 const Sint32 mousey = (inputs.getMouse(player.playernum, Inputs::Y) / (float)yres) * (float)Frame::virtualScreenY;
14120
14121 index = 0;
14122 bool alignRight = getDropDownAlignRight(currentName);
14123 for ( auto& optionGlyphTextBacking : optionFrames )
14124 {
14125 auto img = std::get<0>(optionGlyphTextBacking);
14126 auto txt = std::get<1>(optionGlyphTextBacking);
14127
14128 if ( txt->getHJustify() == Field::justify_t::CENTER || true )
14129 {
14130 SDL_Rect size = txt->getSize();
14131 size.w = maxWidth;
14132 txt->setSize(size);
14133 }
14134
14135 if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
14136 {
14137 auto ml = dropdownFrame->findImage("interact middle left");
14138 SDL_Rect absoluteSize = txt->getAbsoluteSize();
14139 absoluteSize.x -= (4 + ml->pos.w) + (alignRight ? rightClickProtectBuffer : 0);
14140 absoluteSize.w += ((4 + ml->pos.w) * 2 + rightClickProtectBuffer);
14141 absoluteSize.y += 4;
14142 absoluteSize.h -= 4;
14143 if ( mousex >= absoluteSize.x && mousex < absoluteSize.x + absoluteSize.w
14144 && mousey >= absoluteSize.y && mousey < absoluteSize.y + absoluteSize.h )
14145 {
14146 dropDownOptionSelected = index;
14147 }
14148 }
14149 ++index;
14150 }
14151
14152 int textStartX = textPaddingX;
14153 //if ( optionFrames.size() > 0 )
14154 //{
14155 // auto img = std::get<0>(optionFrames[0]);
14156 // textStartX += (img->disabled ? 0 : img->pos.x + img->pos.w);
14157 //}
14158 const int frameWidth = maxWidth + textStartX + textPaddingX;
14159
14160 SDL_Rect frameSize = dropdownFrame->getSize();
14161 frameSize.x = dropDownX;
14162 frameSize.y = dropDownY;
14163
14164 // position the frame elements
14165 int interimHeight = 0;
14166 {
14167 auto tl = dropdownFrame->findImage("interact top left");
14168 auto tmid = dropdownFrame->findImage("interact top background");
14169 tmid->pos.w = frameWidth - tl->pos.w * 2;
14170 auto tr = dropdownFrame->findImage("interact top right");
14171 tr->pos.x = tmid->pos.x + tmid->pos.w;
14172
14173 interimHeight = tmid->pos.y + tmid->pos.h;
14174 frameSize.w = tr->pos.x + tr->pos.w;
14175 }
14176 {
14177 const int middleOffsetY = 13;
14178 auto ml = dropdownFrame->findImage("interact middle left");
14179 ml->pos.h = maxHeight - interimHeight - middleOffsetY;
14180 auto mmid = dropdownFrame->findImage("interact middle background");
14181 mmid->pos.h = ml->pos.h;
14182 mmid->pos.w = frameWidth - (ml->pos.w) * 2;
14183 mmid->color = hudColors.itemContextMenuOptionImg;
14184 auto mr = dropdownFrame->findImage("interact middle right");
14185 mr->pos.h = ml->pos.h;
14186 mr->pos.x = mmid->pos.x + mmid->pos.w;
14187
14188 interimHeight = mmid->pos.y + mmid->pos.h;
14189 }
14190 {
14191 auto bl = dropdownFrame->findImage("interact bottom left");
14192 bl->pos.y = interimHeight;
14193 auto bmid = dropdownFrame->findImage("interact bottom background");
14194 bmid->pos.y = interimHeight;
14195 bmid->pos.w = frameWidth - bl->pos.w * 2;
14196 auto br = dropdownFrame->findImage("interact bottom right");
14197 br->pos.y = interimHeight;
14198 br->pos.x = bmid->pos.x + bmid->pos.w;
14199
14200 frameSize.h = br->pos.y + br->pos.h;
14201 }
14202
14203 if ( auto interactText = dropdownFrame->findField("interact text") )
14204 {
14205 SDL_Rect size = interactText->getSize();
14206 size.x = 0;
14207 size.w = frameSize.w;
14208 interactText->setSize(size);
14209 }
14210
14211 if ( !alignRight )
14212 {
14213 frameSize.x -= frameSize.w;
14214 }
14215 if ( frameSize.y + frameSize.h > player.camera_virtualy2() )
14216 {
14217 frameSize.y -= (frameSize.y + frameSize.h) - player.camera_virtualy2();
14218 }
14219 dropdownFrame->setSize(frameSize);
14220
14221 if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
14222 {
14223 SDL_Rect absoluteSize = dropdownFrame->getAbsoluteSize();
14224 // right click protect uses exact border, else there is 10 px buffer
14225 absoluteSize.x -= (alignRight ? rightClickProtectBuffer : 0);
14226 absoluteSize.w += rightClickProtectBuffer;
14227 if ( !(mousex >= absoluteSize.x && mousex < absoluteSize.x + absoluteSize.w
14228 && mousey >= absoluteSize.y && mousey < absoluteSize.y + absoluteSize.h) )
14229 {
14230 dropDownOptionSelected = -1;
14231 }
14232 }
14233
14234 if ( dropDownOptionSelected >= 0 && dropDownOptionSelected < dropDown.options.size() )
14235 {
14236 auto txt = std::get<1>(optionFrames[dropDownOptionSelected]);
14237 txt->setColor(hudColors.itemContextMenuOptionSelectedText);
14238 SDL_Rect size = txt->getSize();
14239 size.x -= 4;
14240 size.w += 2 * 4;
14241 size.y += 3;
14242 highlightImageMid->pos = size;
14243 highlightImageMid->pos.x += highlightImageLeft->pos.w;
14244 highlightImageMid->pos.w -= 2 * highlightImageLeft->pos.w;
14245 highlightImageMid->pos.h = highlightImageLeft->pos.h;
14246 highlightImageMid->disabled = false;
14247
14248 highlightImageLeft->pos.x = highlightImageMid->pos.x - highlightImageLeft->pos.w;
14249 highlightImageLeft->pos.y = highlightImageMid->pos.y;
14250 highlightImageLeft->disabled = false;
14251 highlightImageRight->pos.x = highlightImageMid->pos.x + highlightImageMid->pos.w;
14252 highlightImageRight->disabled = false;
14253 highlightImageRight->pos.y = highlightImageMid->pos.y;
14254 }
14255
14256 bool activate = false;
14257 if ( !dropDownToggleClick && !Input::inputs[player.playernum].binary("MenuRightClick") )
14258 {
14259 activate = true;
14260 if ( dropDownOptionSelected >= 0 && dropDownOptionSelected < dropDown.options.size() )
14261 {
14262 Player::soundActivate();
14263 }
14264 }
14265 else if ( dropDownToggleClick )
14266 {
14267 if ( Input::inputs[player.playernum].consumeBinaryToggle("MenuRightClick") )
14268 {
14269 close();
14270 }
14271 else if ( Input::inputs[player.playernum].consumeBinaryToggle("MenuConfirm") )
14272 {
14273 activate = true;
14274 if ( dropDownOptionSelected >= 0 && dropDownOptionSelected < dropDown.options.size() )
14275 {
14276 Player::soundActivate();
14277 }
14278 }
14279 else if ( Input::inputs[player.playernum].consumeBinaryToggle("MenuCancel") )
14280 {
14281 close();
14282 Player::soundCancel();
14283 }
14284 }
14285
14286 if ( !(player.bControlEnabled && !gamePaused && !player.usingCommand()) )
14287 {
14288 close();
14289 }
14290 else if ( activate )
14291 {
14292 if ( dropDownOptionSelected >= 0 && dropDownOptionSelected < dropDown.options.size() )
14293 {
14294 if ( currentName == "item_interact" || currentName == "hotbar_interact"
14295 || currentName == "chest_interact" || currentName == "spell_interact" )
14296 {
14297 if ( item && dropDownOptionSelected < contextOptions.size() )
14298 {
14299 if ( contextOptions[dropDownOptionSelected] == ItemContextMenuPrompts::PROMPT_DROP
14300 && player.paperDoll.isItemOnDoll(*item) )
14301 {
14302 // need to unequip
14303 player.inventoryUI.activateItemContextMenuOption(item, ItemContextMenuPrompts::PROMPT_UNEQUIP_FOR_DROP);
14304 player.paperDoll.updateSlots();
14305 if ( player.paperDoll.isItemOnDoll(*item) )
14306 {
14307 // couldn't unequip, no more actions
14308 }
14309 else
14310 {
14311 // successfully unequipped, let's drop it.
14312 bool droppedAll = false;
14313 while ( item && item->count > 1 )
14314 {
14315 droppedAll = dropItem(item, player.playernum);
14316 if ( droppedAll )
14317 {
14318 item = nullptr;
14319 }
14320 }
14321 if ( !droppedAll )
14322 {
14323 dropItem(item, player.playernum);
14324 }
14325 }
14326 }
14327 else
14328 {
14329 player.inventoryUI.activateItemContextMenuOption(item, contextOptions[dropDownOptionSelected]);
14330 }
14331 }
14332 }
14333 else
14334 {
14335 activateSelection(currentName, dropDownOptionSelected);
14336 }
14337 }
14338 //Close the menu.
14339 close();
14340 }
14341}
14342
14343void Player::GUIDropdown_t::close()
14344{
14345 if ( dropdownFrame )
14346 {
14347 dropdownFrame->setHollow(true);
14348 dropdownFrame->removeSelf();
14349 dropdownFrame = nullptr;
14350 }
14351 if ( dropdownBlockClickFrame )
14352 {
14353 dropdownBlockClickFrame->setHollow(true);
14354 dropdownBlockClickFrame->removeSelf();
14355 dropdownBlockClickFrame = nullptr;
14356 }
14357 dropDownOptionSelected = 0;
14358 dropDownItem = 0;
14359 bOpen = false;
14360 dropDownToggleClick = false;
14361 currentName = "";
14362 bClosedThisTick = true;
14363}
14364
14365
14366void Player::GUI_t::closeDropdowns()
14367{
14368 if ( dropdownMenu.bOpen )
14369 {
14370 dropdownMenu.close();
14371 }
14372 if ( inputs.getUIInteraction(player.playernum)->itemMenuOpen )
14373 {
14374 inputs.getUIInteraction(player.playernum)->itemMenuOpen = false;
14375 player.inventoryUI.updateItemContextMenu();
14376 }
14377}
14378bool Player::GUI_t::isDropdownActive()
14379{
14380 if ( dropdownMenu.bOpen )
14381 {
14382 return true;
14383 }
14384 if ( inputs.getUIInteraction(player.playernum)->itemMenuOpen )
14385 {
14386 return true;
14387 }
14388 return false;
14389}
14390
14391void Player::GUIDropdown_t::open(const std::string name)
14392{
14393 set(name);
14394 assert(dropdownFrame)(static_cast<void> (0));
14395 if ( bOpen )
14396 {
14397 return;
14398 }
14399
14400 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor )
14401 {
14402 Player::soundActivate();
14403 }
14404 bOpen = true;
14405 bClosedThisTick = false;
14406
14407 // clear inventory item context menu stuff
14408 bool& itemMenuOpen = inputs.getUIInteraction(player.playernum)->itemMenuOpen;
14409 itemMenuOpen = false;
14410
14411 if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
14412 {
14413 dropDownX = (inputs.getMouse(player.playernum, Inputs::X) / (float)xres) * (float)Frame::virtualScreenX + (getDropDownAlignRight(name) ? 8 : -8);
14414 dropDownY = (inputs.getMouse(player.playernum, Inputs::Y) / (float)yres) * (float)Frame::virtualScreenY;
14415 if ( auto interactMenuTop = dropdownFrame->findImage("interact top background") )
14416 {
14417 // 10px is slot half height, minus the top interact text height
14418 // mouse will be situated halfway in first menu option
14419 dropDownY -= (interactMenuTop->pos.h + 10 + 2);
14420 }
14421 if ( dropDownX % 2 == 1 )
14422 {
14423 ++dropDownX; // even pixel adjustment
14424 }
14425 if ( dropDownY % 2 == 1 )
14426 {
14427 ++dropDownY; // even pixel adjustment
14428 }
14429 }
14430 dropDownOptionSelected = 0;
14431 if ( allDropDowns[name].defaultOption > 0 && allDropDowns[name].options.size() > allDropDowns[name].defaultOption )
14432 {
14433 if ( auto txt = dropdownFrame->findField("interact option 1") )
14434 {
14435 dropDownY -= txt->getSize().h;
14436 dropDownOptionSelected = allDropDowns[name].defaultOption;
14437 }
14438 }
14439 if ( auto highlightImageMid = dropdownFrame->findImage("interact selected highlight mid") )
14440 {
14441 highlightImageMid->disabled = true;
14442 }
14443 if ( auto highlightImageLeft = dropdownFrame->findImage("interact selected highlight left") )
14444 {
14445 highlightImageLeft->disabled = true;
14446 }
14447 if ( auto highlightImageRight = dropdownFrame->findImage("interact selected highlight right") )
14448 {
14449 highlightImageRight->disabled = true;
14450 }
14451
14452 //Default reset. Otherwise will break mouse support after using gamepad once to trigger a context menu.
14453 dropDownToggleClick = false;
14454
14455 if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
14456 {
14457 player.hud.cursor.lastUpdateTick = ticks;
14458 }
14459}
14460
14461void Player::GUIDropdown_t::create(const std::string name)
14462{
14463 if ( dropdownFrame )
14464 {
14465 dropdownFrame->removeSelf();
14466 dropdownFrame = nullptr;
14467 }
14468 if ( dropdownBlockClickFrame )
14469 {
14470 dropdownBlockClickFrame->removeSelf();
14471 dropdownBlockClickFrame = nullptr;
14472 }
14473
14474 auto& dropDown = allDropDowns[name];
14475
14476 char dropdownBlockClickName[64] = "";
14477 snprintf(dropdownBlockClickName, sizeof(dropdownBlockClickName), "player dropdown block click %d", player.playernum);
14478 dropdownBlockClickFrame = gameUIFrame[player.playernum]->addFrame(dropdownBlockClickName);
14479 dropdownBlockClickFrame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(),
14480 players[player.playernum]->camera_virtualy1(),
14481 players[player.playernum]->camera_virtualWidth(),
14482 players[player.playernum]->camera_virtualHeight()});
14483 dropdownBlockClickFrame->setOwner(player.playernum);
14484
14485 char dropdownName[64] = "";
14486 snprintf(dropdownName, sizeof(dropdownName), "player dropdown %d", player.playernum);
14487 dropdownFrame = gameUIFrame[player.playernum]->addFrame(dropdownName);
14488 const int interactWidth = 106;
14489 dropdownFrame->setSize(SDL_Rect{ 0, 0, interactWidth + 6 * 2, 100 });
14490 dropdownFrame->setDisabled(true);
14491 dropdownFrame->setInheritParentFrameOpacity(false);
14492
14493 Uint32 color = makeColor( 255, 255, 255, 255);
14494 const int topBackgroundHeight = 30;
14495 const int optionHeight = 20;
14496
14497 dropdownFrame->addImage(SDL_Rect{ 24, 0, 0, 30 },
14498 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_T03.png", "interact top background");
14499 dropdownFrame->addImage(SDL_Rect{ 0, 0, 24, 30 },
14500 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_TL03.png", "interact top left");
14501 dropdownFrame->addImage(SDL_Rect{ 0, 0, 24, 30 },
14502 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_TR03.png", "interact top right");
14503
14504 dropdownFrame->addImage(SDL_Rect{ 24, 30, 0, 12 },
14505 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_C03.png", "interact middle background");
14506 auto ml = dropdownFrame->addImage(SDL_Rect{ 0, 30, 24, 12 },
14507 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_L03.png", "interact middle left");
14508 ml->tiled = true;
14509 auto mr = dropdownFrame->addImage(SDL_Rect{ 0, 30, 24, 12 },
14510 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_R03.png", "interact middle right");
14511 mr->tiled = true;
14512
14513 dropdownFrame->addImage(SDL_Rect{ 24, 96, 0, 14 },
14514 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_B03.png", "interact bottom background");
14515 dropdownFrame->addImage(SDL_Rect{ 0, 96, 24, 14 },
14516 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_BL03.png", "interact bottom left");
14517 dropdownFrame->addImage(SDL_Rect{ 0, 96, 24, 14 },
14518 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_BR03.png", "interact bottom right");
14519
14520 auto selectmid = dropdownFrame->addImage(SDL_Rect{ 6, optionHeight - 16, interactWidth, 22 },
14521 hudColors.itemContextMenuOptionSelectedImg, "*#images/ui/Inventory/tooltips/HoverItemMenu_SelectBack_M03.png", "interact selected highlight mid");
14522 selectmid->tiled = true;
14523 dropdownFrame->addImage(SDL_Rect{ 6, optionHeight - 16, 20, 22 },
14524 hudColors.itemContextMenuOptionSelectedImg, "*#images/ui/Inventory/tooltips/HoverItemMenu_SelectBack_L03.png", "interact selected highlight left");
14525 dropdownFrame->addImage(SDL_Rect{ 6, optionHeight - 16, 20, 22 },
14526 hudColors.itemContextMenuOptionSelectedImg, "*#images/ui/Inventory/tooltips/HoverItemMenu_SelectBack_R03.png", "interact selected highlight right");
14527
14528 const char* interactFont = "fonts/pixel_maz.ttf#32#2";
14529
14530 auto interactText = dropdownFrame->addField("interact text", 32);
14531 interactText->setText(dropDown.title.c_str());
14532 interactText->setSize(SDL_Rect{ 0, 2, 0, topBackgroundHeight });
14533 interactText->setFont(interactFont);
14534 interactText->setHJustify(Field::justify_t::CENTER);
14535 interactText->setVJustify(Field::justify_t::CENTER);
14536 interactText->setColor(hudColors.itemContextMenuHeadingText);
14537
14538 const int interactOptionStartX = 4;
14539 const int interactOptionStartY = 33;
14540 const int glyphSize = 20;
14541 const int textWidth = 80;
14542 const int textHeight = glyphSize + 8;
14543
14544 Uint32 textColor = hudColors.itemContextMenuOptionText;
14545 SDL_Rect prevGlyphPos;
14546
14547 for ( int i = 1; i <= dropDown.options.size(); ++i )
14548 {
14549 char glyphname[32] = "";
14550 snprintf(glyphname, sizeof(glyphname), "glyph %d", i);
14551 Frame::image_t* interactGlyph = nullptr;
14552 if ( i == 1 )
14553 {
14554 interactGlyph = dropdownFrame->addImage(
14555 SDL_Rect{ interactOptionStartX + 4, interactOptionStartY + 4, glyphSize, glyphSize },
14556 0xFFFFFFFF, "", glyphname);
14557 }
14558 else if ( i > 1 )
14559 {
14560 interactGlyph = dropdownFrame->addImage(
14561 SDL_Rect{ interactOptionStartX + 4,
14562 prevGlyphPos.y + prevGlyphPos.h + 4,
14563 glyphSize, glyphSize },
14564 0xFFFFFFFF, "", glyphname);
14565 }
14566
14567 const int textAlignX = interactGlyph->pos.x + interactGlyph->pos.w + 6;
14568 int textAlignY = interactGlyph->pos.y - 10;
14569 if ( i == 1 )
14570 {
14571 textAlignY = interactGlyph->pos.y - 8;
14572 }
14573
14574 char optionname[32] = "";
14575 snprintf(optionname, sizeof(optionname), "interact option %d", i);
14576 interactText = dropdownFrame->addField(optionname, 32);
14577 interactText->setText("");
14578 interactText->setSize(SDL_Rect{
14579 textAlignX,
14580 textAlignY,
14581 textWidth, textHeight });
14582 interactText->setFont(interactFont);
14583 interactText->setHJustify(Field::justify_t::LEFT);
14584 interactText->setVJustify(Field::justify_t::CENTER);
14585 interactText->setColor(textColor);
14586
14587 prevGlyphPos = interactGlyph->pos;
14588 }
14589}
14590
14591void Player::CharacterSheet_t::selectElement(SheetElements element, bool usingMouse, bool moveCursor)
14592{
14593 selectedElement = element;
14594
14595 Frame* elementFrame = nullptr;
14596 Frame::image_t* img = nullptr;
14597 Field* elementField = nullptr;
14598 Button* elementButton = nullptr;
14599 bool selectedAButton = false;
14600 switch ( element )
14601 {
14602 case SHEET_OPEN_LOG:
14603 if ( elementFrame = sheetFrame->findFrame("log map buttons") )
14604 {
14605 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor )
14606 {
14607 elementFrame->findButton("log button")->select();
14608 selectedAButton = true;
14609 }
14610 elementFrame = elementFrame->findFrame("log button selector");
14611 //img = elementFrame->findImage("log button img");
14612 }
14613 break;
14614 case SHEET_OPEN_MAP:
14615 if ( elementFrame = sheetFrame->findFrame("log map buttons") )
14616 {
14617 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor )
14618 {
14619 elementFrame->findButton("map button")->select();
14620 selectedAButton = true;
14621 }
14622 elementFrame = elementFrame->findFrame("map button selector");
14623 //img = elementFrame->findImage("map button img");
14624 }
14625 break;
14626 case SHEET_SKILL_LIST:
14627 elementFrame = sheetFrame->findFrame("skills button frame");
14628 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor )
14629 {
14630 elementFrame->findButton("skills button")->select();
14631 selectedAButton = true;
14632 }
14633 break;
14634 case SHEET_TIMER:
14635 elementFrame = sheetFrame->findFrame("game timer");
14636 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor )
14637 {
14638 elementFrame->findButton("timer selector")->select();
14639 selectedAButton = true;
14640 }
14641 break;
14642 case SHEET_GOLD:
14643 if ( elementFrame = sheetFrame->findFrame("character info") )
14644 {
14645 if ( elementFrame = elementFrame->findFrame("character info inner frame") )
14646 {
14647 elementButton = elementFrame->findButton("character gold selector");
14648 }
14649 }
14650 break;
14651 case SHEET_DUNGEON_FLOOR:
14652 if ( elementFrame = sheetFrame->findFrame("dungeon floor frame") )
14653 {
14654 elementButton = elementFrame->findButton("dungeon floor selector");
14655 }
14656 break;
14657 case SHEET_CHAR_CLASS:
14658 if ( elementFrame = sheetFrame->findFrame("character info") )
14659 {
14660 if ( elementFrame = elementFrame->findFrame("character info inner frame") )
14661 {
14662 elementButton = elementFrame->findButton("character class selector");
14663 }
14664 }
14665 break;
14666 case SHEET_CHAR_RACE_SEX:
14667 if ( elementFrame = sheetFrame->findFrame("character info") )
14668 {
14669 if ( elementFrame = elementFrame->findFrame("character info inner frame") )
14670 {
14671 elementButton = elementFrame->findButton("character race selector");
14672 }
14673 }
14674 break;
14675 case SHEET_STR:
14676 if ( elementFrame = sheetFrame->findFrame("stats") )
14677 {
14678 if ( elementFrame = elementFrame->findFrame("stats inner frame") )
14679 {
14680 elementButton = elementFrame->findButton("str button");
14681 }
14682 }
14683 break;
14684 case SHEET_DEX:
14685 if ( elementFrame = sheetFrame->findFrame("stats") )
14686 {
14687 if ( elementFrame = elementFrame->findFrame("stats inner frame") )
14688 {
14689 elementButton = elementFrame->findButton("dex button");
14690 }
14691 }
14692 break;
14693 case SHEET_CON:
14694 if ( elementFrame = sheetFrame->findFrame("stats") )
14695 {
14696 if ( elementFrame = elementFrame->findFrame("stats inner frame") )
14697 {
14698 elementButton = elementFrame->findButton("con button");
14699 }
14700 }
14701 break;
14702 case SHEET_INT:
14703 if ( elementFrame = sheetFrame->findFrame("stats") )
14704 {
14705 if ( elementFrame = elementFrame->findFrame("stats inner frame") )
14706 {
14707 elementButton = elementFrame->findButton("int button");
14708 }
14709 }
14710 break;
14711 case SHEET_PER:
14712 if ( elementFrame = sheetFrame->findFrame("stats") )
14713 {
14714 if ( elementFrame = elementFrame->findFrame("stats inner frame") )
14715 {
14716 elementButton = elementFrame->findButton("per button");
14717 }
14718 }
14719 break;
14720 case SHEET_CHR:
14721 if ( elementFrame = sheetFrame->findFrame("stats") )
14722 {
14723 if ( elementFrame = elementFrame->findFrame("stats inner frame") )
14724 {
14725 elementButton = elementFrame->findButton("chr button");
14726 }
14727 }
14728 break;
14729 case SHEET_ATK:
14730 if ( elementFrame = sheetFrame->findFrame("attributes") )
14731 {
14732 if ( elementFrame = elementFrame->findFrame("attributes inner frame") )
14733 {
14734 elementButton = elementFrame->findButton("atk button");
14735 }
14736 }
14737 break;
14738 case SHEET_AC:
14739 if ( elementFrame = sheetFrame->findFrame("attributes") )
14740 {
14741 if ( elementFrame = elementFrame->findFrame("attributes inner frame") )
14742 {
14743 elementButton = elementFrame->findButton("ac button");
14744 }
14745 }
14746 break;
14747 case SHEET_POW:
14748 if ( elementFrame = sheetFrame->findFrame("attributes") )
14749 {
14750 if ( elementFrame = elementFrame->findFrame("attributes inner frame") )
14751 {
14752 elementButton = elementFrame->findButton("pow button");
14753 }
14754 }
14755 break;
14756 case SHEET_RES:
14757 if ( elementFrame = sheetFrame->findFrame("attributes") )
14758 {
14759 if ( elementFrame = elementFrame->findFrame("attributes inner frame") )
14760 {
14761 elementButton = elementFrame->findButton("res button");
14762 }
14763 }
14764 break;
14765 case SHEET_RGN:
14766 if ( elementFrame = sheetFrame->findFrame("attributes") )
14767 {
14768 if ( elementFrame = elementFrame->findFrame("attributes inner frame") )
14769 {
14770 elementButton = elementFrame->findButton("rgn button");
14771 }
14772 }
14773 break;
14774 case SHEET_RGN_MP:
14775 if ( elementFrame = sheetFrame->findFrame("attributes") )
14776 {
14777 if ( elementFrame = elementFrame->findFrame("attributes inner frame") )
14778 {
14779 elementButton = elementFrame->findButton("rgn mp button");
14780 }
14781 }
14782 break;
14783 case SHEET_WGT:
14784 if ( elementFrame = sheetFrame->findFrame("attributes") )
14785 {
14786 if ( elementFrame = elementFrame->findFrame("attributes inner frame") )
14787 {
14788 elementButton = elementFrame->findButton("wgt button");
14789 }
14790 }
14791 break;
14792 default:
14793 elementFrame = nullptr;
14794 break;
14795 }
14796
14797 SDL_Rect pos{ 0, 0, 0, 0 };
14798 if ( elementFrame && moveCursor )
14799 {
14800 elementFrame->warpMouseToFrame(player.playernum, (Inputs::SET_CONTROLLER));
14801 pos = elementFrame->getAbsoluteSize();
14802 if ( img )
14803 {
14804 pos.x += img->pos.x;
14805 pos.y += img->pos.y;
14806 pos.w = img->pos.w;
14807 pos.h = img->pos.h;
14808 }
14809 if ( elementButton )
14810 {
14811 pos = elementButton->getAbsoluteSize();
14812 }
14813 if ( elementField )
14814 {
14815 pos = elementField->getAbsoluteSize();
14816 }
14817 // make sure to adjust absolute size to camera viewport
14818 pos.x -= player.camera_virtualx1();
14819 pos.y -= player.camera_virtualy1();
14820 player.hud.setCursorDisabled(false);
14821 if ( !isInteractable )
14822 {
14823 player.characterSheet.queuedElement = player.characterSheet.selectedElement;
14824 }
14825 else
14826 {
14827 player.characterSheet.queuedElement = SHEET_UNSELECTED;
14828 player.hud.updateCursorAnimation(pos.x - 1, pos.y - 1, pos.w, pos.h, usingMouse);
14829 }
14830 }
14831 if ( !selectedAButton && elementFrame && !inputs.getVirtualMouse(player.playernum)->draw_cursor )
14832 {
14833 if ( elementButton )
14834 {
14835 elementButton->select();
14836 }
14837 else
14838 {
14839 elementFrame->select();
14840 }
14841 }
14842}
14843
14844void Player::CharacterSheet_t::updateGameTimer()
14845{
14846 auto characterInfoFrame = sheetFrame->findFrame("character info");
14847 auto timerFrame = sheetFrame->findFrame("game timer");
14848 auto timerText = timerFrame->findField("timer text");
14849 char buf[32];
14850
14851 Uint32 sec = (completionTime / TICKS_PER_SECOND50) % 60;
14852 Uint32 min = ((completionTime / TICKS_PER_SECOND50) / 60) % 60;
14853 Uint32 hour = (((completionTime / TICKS_PER_SECOND50) / 60) / 60) % 24;
14854 Uint32 day = ((completionTime / TICKS_PER_SECOND50) / 60) / 60 / 24;
14855 snprintf(buf, sizeof(buf), "%02d:%02d:%02d:%02d", day, hour, min, sec);
14856 timerText->setText(buf);
14857
14858 bool enableTooltips = !player.GUI.isDropdownActive() && !player.GUI.dropdownMenu.bClosedThisTick && inputs.getVirtualMouse(player.playernum)->draw_cursor;
14859
14860 bool bCompactView = player.bUseCompactGUIHeight();
14861 if ( bCompactView )
14862 {
14863 enableTooltips = false;
14864 }
14865
14866 if ( selectedElement == SHEET_TIMER && enableTooltips )
14867 {
14868 SDL_Rect tooltipPos = characterInfoFrame->getSize();
14869 tooltipPos.y = timerFrame->getSize().y;
14870 Player::PanelJustify_t tooltipJustify = PANEL_JUSTIFY_RIGHT;
14871 if ( (panelJustify == PANEL_JUSTIFY_LEFT && !bCompactView) || (panelJustify == PANEL_JUSTIFY_RIGHT && bCompactView) )
14872 {
14873 tooltipJustify = PANEL_JUSTIFY_LEFT;
14874 tooltipPos.x += tooltipPos.w;
14875 }
14876 updateCharacterSheetTooltip(SHEET_TIMER, tooltipPos, tooltipJustify);
14877 }
14878}
14879
14880std::string& Player::CharacterSheet_t::getHoverTextString(std::string key)
14881{
14882 if ( hoverTextStrings.find(key) != hoverTextStrings.end() )
14883 {
14884 return hoverTextStrings[key];
14885 }
14886 return defaultString;
14887}
14888
14889bool getAttackTooltipLines(int playernum, AttackHoverText_t& attackHoverTextInfo, int lineNumber, char titleBuf[128], char valueBuf[128])
14890{
14891 std::string skillName = "-";
14892 Sint32 skillLVL = 0;
14893 for ( auto& skill : Player::SkillSheet_t::skillSheetData.skillEntries )
14894 {
14895 if ( skill.skillId == attackHoverTextInfo.proficiency )
14896 {
14897 skillName = skill.name;
14898 skillLVL = stats[playernum]->getModifiedProficiency(attackHoverTextInfo.proficiency);
14899 break;
14900 }
14901 }
14902
14903 if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_MELEE_WEAPON )
14904 {
14905 switch ( lineNumber )
14906 {
14907 case 1:
14908 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_avg").c_str());
14909 snprintf(valueBuf, 127,
14910 Player::CharacterSheet_t::getHoverTextString("attributes_atk_average_format").c_str(),
14911 (real_t)attackHoverTextInfo.attackMinRange + ((attackHoverTextInfo.attackMaxRange - attackHoverTextInfo.attackMinRange) / 2.0));
14912 return true;
14913 case 2:
14914 snprintf(titleBuf, 127, Player::CharacterSheet_t::getHoverTextString("attributes_atk_range").c_str(),
14915 skillName.c_str(), skillLVL);
14916 snprintf(valueBuf, 127,
14917 Player::CharacterSheet_t::getHoverTextString("attributes_atk_range_format").c_str(),
14918 attackHoverTextInfo.attackMinRange, attackHoverTextInfo.attackMaxRange);
14919 return true;
14920 case 3:
14921 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_attr_bonus_melee").c_str());
14922 snprintf(valueBuf, 127,
14923 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
14924 attackHoverTextInfo.mainAttributeBonus);
14925 return true;
14926 case 4:
14927 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_weapon_bonus").c_str());
14928 snprintf(valueBuf, 127,
14929 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
14930 attackHoverTextInfo.weaponBonus);
14931 return true;
14932 case 5:
14933 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_melee_weapon_base").c_str());
14934 snprintf(valueBuf, 127,
14935 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
14936 BASE_MELEE_DAMAGE);
14937 return true;
14938 case 6:
14939 return false;
14940 default:
14941 return false;
14942 }
14943 }
14944 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_UNARMED )
14945 {
14946 switch ( lineNumber )
14947 {
14948 case 1:
14949 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_avg").c_str());
14950 snprintf(valueBuf, 127,
14951 Player::CharacterSheet_t::getHoverTextString("attributes_atk_average_format").c_str(),
14952 (real_t)attackHoverTextInfo.attackMinRange + ((attackHoverTextInfo.attackMaxRange - attackHoverTextInfo.attackMinRange) / 2.0));
14953 return true;
14954 case 2:
14955 snprintf(titleBuf, 127, Player::CharacterSheet_t::getHoverTextString("attributes_atk_range").c_str(),
14956 skillName.c_str(), skillLVL);
14957 snprintf(valueBuf, 127,
14958 Player::CharacterSheet_t::getHoverTextString("attributes_atk_range_format").c_str(),
14959 attackHoverTextInfo.attackMinRange, attackHoverTextInfo.attackMaxRange);
14960 return true;
14961 case 3:
14962 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_attr_bonus_melee").c_str());
14963 snprintf(valueBuf, 127,
14964 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
14965 attackHoverTextInfo.mainAttributeBonus);
14966 return true;
14967 case 4:
14968 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_items_bonus").c_str());
14969 snprintf(valueBuf, 127,
14970 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
14971 attackHoverTextInfo.equipmentAndEffectBonus);
14972 return true;
14973 case 5:
14974 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_skill_bonus").c_str());
14975 snprintf(valueBuf, 127,
14976 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
14977 attackHoverTextInfo.proficiencyBonus);
14978 return true;
14979 case 6:
14980 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_unarmed_weapon_base").c_str());
14981 snprintf(valueBuf, 127,
14982 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
14983 BASE_PLAYER_UNARMED_DAMAGE);
14984 return true;
14985 default:
14986 return false;
14987 }
14988 }
14989 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_PICKAXE )
14990 {
14991 switch ( lineNumber )
14992 {
14993 case 1:
14994 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_avg").c_str());
14995 snprintf(valueBuf, 127,
14996 Player::CharacterSheet_t::getHoverTextString("attributes_atk_average_format").c_str(),
14997 (real_t)attackHoverTextInfo.attackMinRange + ((attackHoverTextInfo.attackMaxRange - attackHoverTextInfo.attackMinRange) / 2.0));
14998 return true;
14999 case 2:
15000 snprintf(titleBuf, 127, Player::CharacterSheet_t::getHoverTextString("attributes_atk_range_noskill").c_str());
15001 snprintf(valueBuf, 127,
15002 Player::CharacterSheet_t::getHoverTextString("attributes_atk_range_format").c_str(),
15003 attackHoverTextInfo.attackMinRange, attackHoverTextInfo.attackMaxRange);
15004 return true;
15005 case 3:
15006 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_attr_bonus_melee").c_str());
15007 snprintf(valueBuf, 127,
15008 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15009 attackHoverTextInfo.mainAttributeBonus);
15010 return true;
15011 case 4:
15012 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_weapon_bonus").c_str());
15013 snprintf(valueBuf, 127,
15014 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15015 attackHoverTextInfo.weaponBonus);
15016 return true;
15017 case 5:
15018 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_melee_weapon_base").c_str());
15019 snprintf(valueBuf, 127,
15020 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15021 BASE_MELEE_DAMAGE);
15022 return true;
15023 default:
15024 return false;
15025 }
15026 }
15027 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_RANGED )
15028 {
15029 switch ( lineNumber )
15030 {
15031 case 1:
15032 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_avg").c_str());
15033 snprintf(valueBuf, 127,
15034 Player::CharacterSheet_t::getHoverTextString("attributes_atk_average_format").c_str(),
15035 (real_t)attackHoverTextInfo.attackMinRange + ((attackHoverTextInfo.attackMaxRange - attackHoverTextInfo.attackMinRange) / 2.0));
15036 return true;
15037 case 2:
15038 snprintf(titleBuf, 127, Player::CharacterSheet_t::getHoverTextString("attributes_atk_range").c_str(),
15039 skillName.c_str(), skillLVL);
15040 snprintf(valueBuf, 127,
15041 Player::CharacterSheet_t::getHoverTextString("attributes_atk_range_format").c_str(),
15042 attackHoverTextInfo.attackMinRange, attackHoverTextInfo.attackMaxRange);
15043 return true;
15044 case 3:
15045 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_attr_bonus_ranged").c_str());
15046 snprintf(valueBuf, 127,
15047 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15048 attackHoverTextInfo.mainAttributeBonus);
15049 return true;
15050 case 4:
15051 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_weapon_bonus").c_str());
15052 snprintf(valueBuf, 127,
15053 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15054 attackHoverTextInfo.weaponBonus);
15055 return true;
15056 case 5:
15057 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_items_bonus").c_str());
15058 snprintf(valueBuf, 127,
15059 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15060 attackHoverTextInfo.equipmentAndEffectBonus);
15061 return true;
15062 case 6:
15063 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_ranged_weapon_base").c_str());
15064 snprintf(valueBuf, 127,
15065 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15066 BASE_RANGED_DAMAGE);
15067 return true;
15068 default:
15069 return false;
15070 }
15071 }
15072 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_THROWN
15073 || attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_THROWN_GEM )
15074 {
15075 switch ( lineNumber )
15076 {
15077 case 1:
15078 snprintf(titleBuf, 127, Player::CharacterSheet_t::getHoverTextString("attributes_atk_range").c_str(),
15079 skillName.c_str(), skillLVL);
15080 snprintf(valueBuf, 127,
15081 Player::CharacterSheet_t::getHoverTextString("attributes_atk_range_format").c_str(),
15082 attackHoverTextInfo.attackMinRange, attackHoverTextInfo.attackMaxRange);
15083 return true;
15084 case 2:
15085 strcpy(titleBuf, "");
15086 strcpy(valueBuf, "");
15087 return true;
15088 case 3:
15089 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_attr_bonus_ranged").c_str());
15090 snprintf(valueBuf, 127,
15091 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15092 attackHoverTextInfo.mainAttributeBonus);
15093 return true;
15094 case 4:
15095 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_weapon_bonus").c_str());
15096 snprintf(valueBuf, 127,
15097 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15098 attackHoverTextInfo.weaponBonus);
15099 return true;
15100 case 5:
15101 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_skill_bonus").c_str());
15102 snprintf(valueBuf, 127,
15103 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15104 attackHoverTextInfo.proficiencyBonus);
15105 return true;
15106 case 6:
15107 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_thrown_weapon_fully_charged").c_str());
15108 snprintf(valueBuf, 127,
15109 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15110 3);
15111 return true;
15112 case 7:
15113 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_thrown_weapon_base").c_str());
15114 snprintf(valueBuf, 127,
15115 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15116 BASE_THROWN_DAMAGE);
15117 return true;
15118 default:
15119 return false;
15120 }
15121 }
15122 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_THROWN_POTION )
15123 {
15124 switch ( lineNumber )
15125 {
15126 case 1:
15127 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_avg").c_str());
15128 snprintf(valueBuf, 127,
15129 Player::CharacterSheet_t::getHoverTextString("attributes_atk_average_format").c_str(),
15130 (real_t)attackHoverTextInfo.attackMinRange + ((attackHoverTextInfo.attackMaxRange - attackHoverTextInfo.attackMinRange) / 2.0));
15131 return true;
15132 case 2:
15133 snprintf(titleBuf, 127, Player::CharacterSheet_t::getHoverTextString("attributes_atk_range").c_str(),
15134 skillName.c_str(), skillLVL);
15135 snprintf(valueBuf, 127,
15136 Player::CharacterSheet_t::getHoverTextString("attributes_atk_range_format").c_str(),
15137 attackHoverTextInfo.attackMinRange, attackHoverTextInfo.attackMaxRange);
15138 return true;
15139 case 3:
15140 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_weapon_bonus").c_str());
15141 snprintf(valueBuf, 127,
15142 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15143 attackHoverTextInfo.weaponBonus);
15144 return true;
15145 case 4:
15146 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_skill_bonus").c_str());
15147 snprintf(valueBuf, 127,
15148 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15149 attackHoverTextInfo.proficiencyBonus);
15150 return true;
15151 case 5:
15152 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_thrown_weapon_base").c_str());
15153 snprintf(valueBuf, 127,
15154 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15155 BASE_THROWN_DAMAGE);
15156 return true;
15157 default:
15158 return false;
15159 }
15160 }
15161 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_WHIP )
15162 {
15163 switch ( lineNumber )
15164 {
15165 case 1:
15166 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_avg").c_str());
15167 snprintf(valueBuf, 127,
15168 Player::CharacterSheet_t::getHoverTextString("attributes_atk_nobonus_format").c_str(),
15169 attackHoverTextInfo.totalAttack);
15170 return true;
15171 case 2:
15172 snprintf(titleBuf, 127, Player::CharacterSheet_t::getHoverTextString("attributes_atk_range").c_str(),
15173 skillName.c_str(), skillLVL);
15174 snprintf(valueBuf, 127,
15175 Player::CharacterSheet_t::getHoverTextString("attributes_atk_range_format").c_str(),
15176 attackHoverTextInfo.attackMinRange, attackHoverTextInfo.attackMaxRange);
15177 return true;
15178 case 3:
15179 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_attr_bonus_whip").c_str());
15180 snprintf(valueBuf, 127,
15181 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15182 attackHoverTextInfo.mainAttributeBonus);
15183 return true;
15184 case 4:
15185 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_weapon_bonus").c_str());
15186 snprintf(valueBuf, 127,
15187 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15188 attackHoverTextInfo.weaponBonus);
15189 return true;
15190 case 5:
15191 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_entry_melee_weapon_base").c_str());
15192 snprintf(valueBuf, 127,
15193 Player::CharacterSheet_t::getHoverTextString("attributes_atk_bonus_format").c_str(),
15194 BASE_MELEE_DAMAGE);
15195 return true;
15196 default:
15197 return false;
15198 }
15199 }
15200 else
15201 {
15202 switch ( lineNumber )
15203 {
15204 case 1:
15205 snprintf(titleBuf, 127, "%s", Player::CharacterSheet_t::getHoverTextString("attributes_atk_avg").c_str());
15206 snprintf(valueBuf, 127, "-");
15207 return true;
15208 case 2:
15209 strcpy(titleBuf, "");
15210 strcpy(valueBuf, "");
15211 return true;
15212 default:
15213 return false;
15214 }
15215 }
15216 return false;
15217}
15218
15219real_t getDisplayedHPRegen(Entity* my, Stat& myStats, Uint32* outColor, char buf[32])
15220{
15221 real_t regen = 0.0;
15222 if ( outColor )
15223 {
15224 *outColor = hudColors.characterSheetNeutral;
15225 }
15226 if ( myStats.HP > 0 )
15227 {
15228 regen = (static_cast<real_t>(Entity::getHealthRegenInterval(my,
15229 myStats, true)) / TICKS_PER_SECOND50);
15230 if ( myStats.type == SKELETON )
15231 {
15232 if ( !(svFlags & SV_FLAG_HUNGER) )
15233 {
15234 regen = HEAL_TIME600 * 4 / TICKS_PER_SECOND50;
15235 }
15236 }
15237 if ( regen < 0 )
15238 {
15239 regen = 0.0;
15240 if ( !(svFlags & SV_FLAG_HUNGER) )
15241 {
15242 if ( outColor )
15243 {
15244 *outColor = hudColors.characterSheetNeutral;
15245 }
15246 }
15247 else
15248 {
15249 if ( outColor )
15250 {
15251 *outColor = hudColors.characterSheetRed;
15252 }
15253 }
15254 }
15255 else if ( regen < HEAL_TIME600 / TICKS_PER_SECOND50 )
15256 {
15257 if ( outColor )
15258 {
15259 *outColor = hudColors.characterSheetGreen;
15260 }
15261 }
15262 }
15263 else
15264 {
15265 regen = HEAL_TIME600 / TICKS_PER_SECOND50;
15266 }
15267
15268 if ( regen > 0.01 )
15269 {
15270 real_t nominalRegen = HEAL_TIME600 / TICKS_PER_SECOND50;
15271 regen = nominalRegen / regen;
15272 }
15273 if ( buf )
15274 {
15275 if ( !(svFlags & SV_FLAG_HUNGER) )
15276 {
15277 snprintf(buf, 32, "- ");
15278 }
15279 else
15280 {
15281 snprintf(buf, 32, "%.f%%", regen * 100.0);
15282 }
15283 }
15284 return regen * 100.0;
15285}
15286
15287real_t getDisplayedMPRegen(Entity* my, Stat& myStats, Uint32* outColor, char buf[32])
15288{
15289 real_t regen = 0.0;
15290 bool isNegative = false;
15291 bool isInsectoid = false;
15292 if ( /*players[player.playernum]->entity*/ true )
15293 {
15294 regen = (static_cast<real_t>(Entity::getManaRegenInterval(my, myStats, true)) / TICKS_PER_SECOND50);
15295 if ( myStats.type == AUTOMATON )
15296 {
15297 if ( myStats.HUNGER <= 300 )
15298 {
15299 isNegative = true;
15300 regen /= 6; // degrade faster
15301 }
15302 else if ( myStats.HUNGER > 1200 )
15303 {
15304 if ( myStats.MP / static_cast<real_t>(std::max(1, myStats.MAXMP)) <= 0.5 )
15305 {
15306 regen /= 4; // increase faster at < 50% mana
15307 }
15308 else
15309 {
15310 regen /= 2; // increase less faster at > 50% mana
15311 }
15312 }
15313 else if ( myStats.HUNGER > 300 )
15314 {
15315 // normal manaRegenInterval 300-1200 hunger.
15316 }
15317 }
15318
15319 if ( regen < 0.0 /*stats[player]->playerRace == RACE_INSECTOID && stats[player]->appearance == 0*/ )
15320 {
15321 regen = 0.0;
15322 }
15323
15324 if ( myStats.type == AUTOMATON )
15325 {
15326 if ( myStats.HUNGER <= 300 )
15327 {
15328 if ( outColor )
15329 {
15330 *outColor = hudColors.characterSheetRed;
15331 }
15332 }
15333 else if ( regen < static_cast<real_t>(getBaseManaRegen(my, myStats)) / TICKS_PER_SECOND50 )
15334 {
15335 if ( outColor )
15336 {
15337 *outColor = hudColors.characterSheetGreen;
15338 }
15339 }
15340 }
15341 else if ( myStats.playerRace == RACE_INSECTOID && myStats.appearance == 0 )
15342 {
15343 isInsectoid = true;
15344 if ( !(svFlags & SV_FLAG_HUNGER) )
15345 {
15346 if ( outColor )
15347 {
15348 *outColor = hudColors.characterSheetNeutral;
15349 }
15350 }
15351 else
15352 {
15353 if ( outColor )
15354 {
15355 *outColor = hudColors.characterSheetRed;
15356 }
15357 }
15358 }
15359 else if ( regen < static_cast<real_t>(getBaseManaRegen(my, myStats)) / TICKS_PER_SECOND50 )
15360 {
15361 if ( outColor )
15362 {
15363 *outColor = hudColors.characterSheetGreen;
15364 }
15365 }
15366 }
15367 else
15368 {
15369 regen = MAGIC_REGEN_TIME300 / TICKS_PER_SECOND50;
15370 }
15371
15372 if ( isNegative )
15373 {
15374 regen *= -1; // negative
15375 }
15376
15377 if ( isInsectoid )
15378 {
15379 if ( svFlags & SV_FLAG_HUNGER )
15380 {
15381 real_t normalRegenTime = (1000.f * 30 * 1.5) / static_cast<float>(TICKS_PER_SECOND50); // 30 base, insectoid does 1.5x in getHungerTickRate()
15382 normalRegenTime /= (std::max(myStats.MAXMP, 1)); // time for 1 mana in seconds
15383 normalRegenTime *= TICKS_PER_SECOND50; // game ticks for 1 mana
15384
15385 regen = normalRegenTime / (regen * TICKS_PER_SECOND50);
15386 }
15387 else
15388 {
15389 if ( buf )
15390 {
15391 snprintf(buf, 32, "-");
15392 }
15393 return regen * 100.0;
15394 }
15395 }
15396 else
15397 {
15398 if ( regen > 0.01 || regen < -0.01 )
15399 {
15400 real_t nominalRegen = MAGIC_REGEN_TIME300 / TICKS_PER_SECOND50;
15401 regen = nominalRegen / regen;
15402 }
15403 }
15404 if ( buf )
15405 {
15406 if ( myStats.type == AUTOMATON )
15407 {
15408 snprintf(buf, 32, Player::CharacterSheet_t::getHoverTextString("attributes_rgn_ht_small_format").c_str(), regen * 100.0);
15409 }
15410 else
15411 {
15412 snprintf(buf, 32, Player::CharacterSheet_t::getHoverTextString("attributes_rgn_small_format").c_str(), regen * 100.0);
15413 }
15414 }
15415 return regen * 100.0;
15416}
15417
15418struct CharacterSheetTooltipCache_t
15419{
15420 Sint32 baseSTR = 0;
15421 Sint32 baseDEX = 0;
15422 Sint32 baseINT = 0;
15423 Sint32 baseCON = 0;
15424 Sint32 basePER = 0;
15425 Sint32 baseCHR = 0;
15426
15427 Sint32 modifiedSTR = 0;
15428 Sint32 modifiedDEX = 0;
15429 Sint32 modifiedINT = 0;
15430 Sint32 modifiedCON = 0;
15431 Sint32 modifiedPER = 0;
15432 Sint32 modifiedCHR = 0;
15433
15434 Sint32 ac = 0;
15435 int playerRace = RACE_HUMAN;
15436 int playerRaceType = NOTHING;
15437 int type = NOTHING;
15438 int classnum = -1;
15439 bool hungerEnabled = false;
15440 int weapontype = 0;
15441 Sint32 ATK = -1;
15442 bool manualUpdate = false;
15443
15444 struct TextEntries_t
15445 {
15446 std::string title = "";
15447 std::string entry1 = "";
15448 std::string entry2 = "";
15449 std::string entry3 = "";
15450 std::string entry4 = "";
15451 std::string entry5 = "";
15452 std::string entry6 = "";
15453 std::string entry7 = "";
15454 std::string entry8 = "";
15455 std::string entry9 = "";
15456 std::string entry10 = "";
15457 std::string entry11 = "";
15458 std::string entry12 = "";
15459 };
15460 TextEntries_t textEntries[Player::CharacterSheet_t::SHEET_ENUM_END];
15461 bool needsUpdate(const int player)
15462 {
15463 if ( manualUpdate ) { return true; }
15464 if ( baseSTR != stats[player]->STR ) { return true; }
15465 if ( baseDEX != stats[player]->DEX ) { return true; }
15466 if ( baseCON != stats[player]->CON ) { return true; }
15467 if ( baseINT != stats[player]->INT ) { return true; }
15468 if ( basePER != stats[player]->PER ) { return true; }
15469 if ( baseCHR != stats[player]->CHR ) { return true; }
15470
15471 if ( modifiedSTR != statGetSTR(stats[player], players[player]->entity) ) { return true; }
15472 if ( modifiedDEX != statGetDEX(stats[player], players[player]->entity) ) { return true; }
15473 if ( modifiedCON != statGetCON(stats[player], players[player]->entity) ) { return true; }
15474 if ( modifiedINT != statGetINT(stats[player], players[player]->entity) ) { return true; }
15475 if ( modifiedPER != statGetPER(stats[player], players[player]->entity) ) { return true; }
15476 if ( modifiedCHR != statGetCHR(stats[player], players[player]->entity) ) { return true; }
15477
15478 if ( playerRace != stats[player]->playerRace ) { return true; }
15479 if ( hungerEnabled != (svFlags & SV_FLAG_HUNGER) ) { return true; }
15480 if ( ac != AC(stats[player]) ) { return true; }
15481 if ( weapontype != getWeaponSkill(stats[player]->weapon) ) { return true; }
15482
15483 int playerRaceType = getMonsterFromPlayerRace(playerRace);
15484 int type = stats[player]->type;
15485 if ( arachnophobia_filter )
15486 {
15487 if ( type == SPIDER )
15488 {
15489 type = CRAB;
15490 }
15491 if ( playerRaceType == SPIDER )
15492 {
15493 playerRaceType = CRAB;
15494 }
15495 }
15496 if ( type != this->type ) { return true; }
15497 if ( playerRaceType != this->playerRaceType ) { return true; }
15498 if ( client_classes[player] != classnum ) { return true; }
15499 return false;
15500 }
15501 void updateToCharacter(const int player)
15502 {
15503 manualUpdate = false;
15504
15505 baseSTR = stats[player]->STR;
15506 baseDEX = stats[player]->DEX;
15507 baseCON = stats[player]->CON;
15508 baseINT = stats[player]->INT;
15509 basePER = stats[player]->PER;
15510 baseCHR = stats[player]->CHR;
15511
15512 modifiedSTR = statGetSTR(stats[player], players[player]->entity);
15513 modifiedDEX = statGetDEX(stats[player], players[player]->entity);
15514 modifiedCON = statGetCON(stats[player], players[player]->entity);
15515 modifiedINT = statGetINT(stats[player], players[player]->entity);
15516 modifiedPER = statGetPER(stats[player], players[player]->entity);
15517 modifiedCHR = statGetCHR(stats[player], players[player]->entity);
15518
15519 playerRace = stats[player]->playerRace;
15520 playerRaceType = getMonsterFromPlayerRace(playerRace);
15521 type = stats[player]->type;
15522 classnum = client_classes[player];
15523 if ( arachnophobia_filter )
15524 {
15525 if ( type == SPIDER )
15526 {
15527 type = CRAB;
15528 }
15529 if ( playerRaceType == SPIDER )
15530 {
15531 playerRaceType = CRAB;
15532 }
15533 }
15534 hungerEnabled = (svFlags & SV_FLAG_HUNGER);
15535 ac = AC(stats[player]);
15536 weapontype = getWeaponSkill(stats[player]->weapon);
15537 }
15538};
15539bool blitCharacterSheetTooltipToSurf = false;
15540CharacterSheetTooltipCache_t charsheetTooltipCache[MAXPLAYERS4];
15541void Player::CharacterSheet_t::updateCharacterSheetTooltip(SheetElements element, SDL_Rect pos, Player::PanelJustify_t tooltipJustify)
15542{
15543 if ( !sheetFrame )
15544 {
15545 return;
15546 }
15547 auto tooltipFrame = sheetFrame->findFrame("sheet tooltip");
15548 if ( tooltipFrame->isDisabled() )
15549 {
15550 tooltipOpacitySetpoint = 0;
15551 tooltipOpacityAnimate = 1.0;
15552 }
15553
15554 if ( static_cast<int>(tooltipFrame->getOpacity()) != tooltipOpacitySetpoint )
15555 {
15556 const real_t fpsScale = getFPSScale(144.0);
15557 if ( tooltipOpacitySetpoint == 0 )
15558 {
15559 if ( ticks - tooltipDeselectedTick > 5 )
15560 {
15561 real_t factor = 10.0;
15562 real_t setpointDiff = fpsScale * std::max(.05, (tooltipOpacityAnimate)) / (factor);
15563 tooltipOpacityAnimate -= setpointDiff;
15564 tooltipOpacityAnimate = std::max(0.0, tooltipOpacityAnimate);
15565 }
15566 }
15567 else
15568 {
15569 real_t setpointDiff = fpsScale * std::max(.05, (1.0 - tooltipOpacityAnimate)) / (1);
15570 tooltipOpacityAnimate += setpointDiff;
15571 tooltipOpacityAnimate = std::min(1.0, tooltipOpacityAnimate);
15572 }
15573 tooltipFrame->setOpacity(tooltipOpacityAnimate * 100);
15574 }
15575 else
15576 {
15577 tooltipFrame->setOpacity(tooltipOpacitySetpoint);
15578 }
15579
15580 if ( selectedElement == SHEET_UNSELECTED )
15581 {
15582 cachedElementTooltip = SHEET_UNSELECTED;
15583 }
15584 if ( player.shootmode )
15585 {
15586 tooltipOpacitySetpoint = 0;
15587 tooltipOpacityAnimate = 0.0;
15588 }
15589 if ( element == SHEET_ENUM_END || element == SHEET_UNSELECTED
15590 || player.GUI.activeModule != Player::GUI_t::MODULE_CHARACTERSHEET )
15591 {
15592 //tooltipFrame->setDisabled(true);
15593 return;
15594 }
15595
15596 tooltipOpacitySetpoint = 0;
15597 tooltipOpacityAnimate = 1.0;
15598 tooltipFrame->setDisabled(false);
15599 tooltipFrame->setOpacity(100.0);
15600 tooltipDeselectedTick = ticks;
15601
15602 //if ( keystatus[SDLK_g] )
15603 //{
15604 // keystatus[SDLK_g] = 0;
15605 // blitCharacterSheetTooltipToSurf = !blitCharacterSheetTooltipToSurf;
15606 // messagePlayer(0, MESSAGE_DEBUG, "%d", blitCharacterSheetTooltipToSurf);
15607 // if ( !blitCharacterSheetTooltipToSurf )
15608 // {
15609 // tooltipFrame->setBlitChildren(false);
15610 // }
15611 // else if ( blitCharacterSheetTooltipToSurf )
15612 // {
15613 // tooltipFrame->setBlitChildren(true);
15614 // }
15615 //}
15616
15617 bool redraw = false;
15618 if ( cachedElementTooltip != selectedElement
15619 || cachedElementTooltip == SHEET_ENUM_END || cachedElementTooltip == SHEET_UNSELECTED
15620 || charsheetTooltipCache[player.playernum].needsUpdate(player.playernum) )
15621 {
15622 redraw = true;
15623 cachedElementTooltip = selectedElement;
15624 charsheetTooltipCache[player.playernum].updateToCharacter(player.playernum);
15625 }
15626
15627 if ( !redraw )
15628 {
15629 return;
15630 }
15631
15632
15633 Uint32 defaultColor = hudColors.characterSheetNeutral;
15634 auto txt = tooltipFrame->findField("tooltip text");
15635 txt->setColor(defaultColor);
15636 auto txtRightAlignHint = tooltipFrame->findField("tooltip text right align hint");
15637 txtRightAlignHint->setColor(hudColors.characterSheetFaintText);
15638 txtRightAlignHint->setDisabled(true);
15639
15640 for ( int i = 1; i <= NUM_CHARSHEET_TOOLTIP_TEXT_FIELDS; ++i )
15641 {
15642 char glyphName[32] = "";
15643 snprintf(glyphName, sizeof(glyphName), "glyph %d", i);
15644 if ( auto glyph = tooltipFrame->findImage(glyphName) )
15645 {
15646 glyph->disabled = true;
15647 }
15648
15649 auto entry = characterSheetTooltipTextFields[player.playernum][i]; assert(entry)(static_cast<void> (0));
15650 entry->setDisabled(true);
15651 entry->setHJustify(Frame::justify_t::LEFT);
15652 entry->setVJustify(Field::justify_t::CENTER);
15653 entry->setColor(defaultColor);
15654 }
15655 auto div = tooltipFrame->findImage("tooltip divider 1");
15656 div->disabled = true;
15657 auto div2 = tooltipFrame->findImage("tooltip divider 2");
15658 div2->disabled = true;
15659
15660 for ( int i = 1; i <= NUM_CHARSHEET_TOOLTIP_BACKING_FRAMES; ++i )
15661 {
15662 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][i];
15663 txtValueBackingFrame->setDisabled(true);
15664 }
15665
15666 auto raceTooltip = tooltipFrame->findFrame("sheet race tooltip");
15667 raceTooltip->setDisabled(true);
15668 auto classTooltip = tooltipFrame->findFrame("sheet class tooltip");
15669 classTooltip->setDisabled(true);
15670
15671 if ( !(element >= Player::CharacterSheet_t::SHEET_STR && element <= Player::CharacterSheet_t::SHEET_CHR) )
15672 {
15673 auto tooltipTopLeft = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
15674 tooltipTopLeft->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TL_00.png";
15675 auto tooltipTop = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP].c_str());
15676 tooltipTop->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_T_00.png";
15677 auto tooltipTopRight = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
15678 tooltipTopRight->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TR_00.png";
15679 imageSetWidthHeight9x9(tooltipFrame, skillsheetEffectBackgroundImages);
15680 }
15681
15682 if ( element >= Player::CharacterSheet_t::SHEET_STR && element <= Player::CharacterSheet_t::SHEET_CHR )
15683 {
15684 auto tooltipTopLeft = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
15685 tooltipTopLeft->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TL_Blue_00.png";
15686 auto tooltipTop = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP].c_str());
15687 tooltipTop->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_T_Blue_00.png";
15688 auto tooltipTopRight = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
15689 tooltipTopRight->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TR_Blue_00.png";
15690 imageSetWidthHeight9x9(tooltipFrame, skillsheetEffectBackgroundImages);
15691
15692 int maxWidth = 260;
15693 if ( getHoverTextString("stat_max_tooltip_width") != defaultString )
15694 {
15695 maxWidth = std::max(0, std::stoi(getHoverTextString("stat_max_tooltip_width")));
15696 }
15697 int minWidth = 0;
15698 if ( getHoverTextString("stat_min_tooltip_width") != defaultString )
15699 {
15700 minWidth = std::max(0, std::stoi(getHoverTextString("stat_min_tooltip_width")));
15701 }
15702 const int padx = 16;
15703 const int pady1 = 8;
15704 const int pady2 = 4;
15705 const int padxMid = 4;
15706 const int padyMid = 8;
15707 SDL_Rect tooltipPos = SDL_Rect{ 400, 0, maxWidth, 100 };
15708
15709 std::string titleText = "";
15710 std::string descText = "";
15711 int value = 0;
15712 switch ( element )
15713 {
15714 case SHEET_STR:
15715 titleText = getHoverTextString("stat_str_title");
15716 descText = getHoverTextString("stat_str_desc");
15717 break;
15718 case SHEET_DEX:
15719 titleText = getHoverTextString("stat_dex_title");
15720 descText = getHoverTextString("stat_dex_desc");
15721 break;
15722 case SHEET_CON:
15723 titleText = getHoverTextString("stat_con_title");
15724 descText = getHoverTextString("stat_con_desc");
15725 break;
15726 case SHEET_INT:
15727 titleText = getHoverTextString("stat_int_title");
15728 descText = getHoverTextString("stat_int_desc");
15729 break;
15730 case SHEET_PER:
15731 titleText = getHoverTextString("stat_per_title");
15732 descText = getHoverTextString("stat_per_desc");
15733 break;
15734 case SHEET_CHR:
15735 titleText = getHoverTextString("stat_chr_title");
15736 descText = getHoverTextString("stat_chr_desc");
15737 break;
15738 default:
15739 break;
15740 }
15741
15742 txt->setText(titleText.c_str());
15743 SDL_Rect txtPos = SDL_Rect{ padx, pady1 - 2, maxWidth - padx * 2, 80 };
15744 txt->setSize(txtPos);
15745 if ( charsheetTooltipCache[player.playernum].textEntries[element].title != txt->getText() )
15746 {
15747 txt->reflowTextToFit(0);
15748 charsheetTooltipCache[player.playernum].textEntries[element].title = txt->getText();
15749 }
15750 txt->setColor(hudColors.characterSheetHeadingText);
15751 Font* actualFont = Font::get(txt->getFont());
15752 int txtHeight = txt->getNumTextLines() * actualFont->height(true);
15753 txtPos.h = txtHeight + 4;
15754 auto txtGet = Text::get(txt->getLongestLine().c_str(), txt->getFont(),
15755 txt->getTextColor(), txt->getOutlineColor());
15756 txtPos.w = txtGet->getWidth();
15757 txtPos.w = std::max(minWidth - padx * 2, txtPos.w);
15758 txt->setSize(txtPos);
15759
15760 tooltipPos.w = (txtPos.w + padx * 2);
15761
15762 unsigned int longestValue = 0;
15763 std::map<int, std::pair<Field*, SDL_Rect>> valueSizes;
15764
15765 int currentHeight = txtPos.y + (actualFont->height(true) * 1) + 2;
15766 const int extraTextHeightForLowerCharacters = 4;
15767 {
15768 currentHeight += padyMid;
15769 auto entry = characterSheetTooltipTextFields[player.playernum][1]; assert(entry)(static_cast<void> (0));
15770 entry->setDisabled(false);
15771 char buf[128] = "";
15772 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_base_amount").c_str());
15773 entry->setText(buf);
15774 entry->setVJustify(Field::justify_t::TOP);
15775
15776 auto glyphBacking = tooltipFrame->findImage("glyph 1");
15777 glyphBacking->disabled = false;
15778 glyphBacking->path = getHoverTextString("icon_backing_path");
15779 glyphBacking->pos.x = padx + padxMid + 4;
15780 glyphBacking->pos.y = currentHeight + 6;
15781 glyphBacking->pos.w = 44;
15782 glyphBacking->pos.h = 44;
15783
15784 auto glyphIcon = tooltipFrame->findImage("glyph 2");
15785 glyphIcon->disabled = false;
15786 glyphIcon->path = "";
15787 switch ( element )
15788 {
15789 case SHEET_STR:
15790 glyphIcon->path = getHoverTextString("icon_str_path");
15791 break;
15792 case SHEET_DEX:
15793 glyphIcon->path = getHoverTextString("icon_dex_path");
15794 break;
15795 case SHEET_CON:
15796 glyphIcon->path = getHoverTextString("icon_con_path");
15797 break;
15798 case SHEET_INT:
15799 glyphIcon->path = getHoverTextString("icon_int_path");
15800 break;
15801 case SHEET_PER:
15802 glyphIcon->path = getHoverTextString("icon_per_path");
15803 break;
15804 case SHEET_CHR:
15805 glyphIcon->path = getHoverTextString("icon_chr_path");
15806 break;
15807 default:
15808 break;
15809 }
15810 glyphIcon->pos.w = 24;
15811 glyphIcon->pos.h = 24;
15812 glyphIcon->pos.x = glyphBacking->pos.x + glyphBacking->pos.w / 2 - glyphIcon->pos.w / 2;
15813 glyphIcon->pos.y = glyphBacking->pos.y + glyphBacking->pos.h / 2 - glyphIcon->pos.h / 2;
15814
15815 SDL_Rect entryPos = entry->getSize();
15816 entryPos.x = padx / 2 + glyphBacking->pos.x + glyphBacking->pos.w;
15817 entryPos.y = currentHeight;
15818 entryPos.w = txtPos.w - (padxMid + glyphBacking->pos.x + glyphBacking->pos.w);
15819 entry->setSize(entryPos);
15820 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry1 != entry->getText() )
15821 {
15822 entry->reflowTextToFit(0);
15823 charsheetTooltipCache[player.playernum].textEntries[element].entry1 = entry->getText();
15824 }
15825 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
15826 entry->setSize(entryPos);
15827 entry->setColor(defaultColor);
15828 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
15829 tooltipPos.h = pady1 + currentHeight + pady2;
15830
15831 auto entryValue = characterSheetTooltipTextFields[player.playernum][2]; assert(entry)(static_cast<void> (0));
15832 entryValue->setDisabled(false);
15833 int value = 0;
15834 char valueBuf[128] = "";
15835 switch ( element )
15836 {
15837 case SHEET_STR:
15838 value = stats[player.playernum]->STR;
15839 snprintf(valueBuf, sizeof(valueBuf), "%d STR", value);
15840 break;
15841 case SHEET_DEX:
15842 value = stats[player.playernum]->DEX;
15843 snprintf(valueBuf, sizeof(valueBuf), "%d DEX", value);
15844 break;
15845 case SHEET_CON:
15846 value = stats[player.playernum]->CON;
15847 snprintf(valueBuf, sizeof(valueBuf), "%d CON", value);
15848 break;
15849 case SHEET_INT:
15850 value = stats[player.playernum]->INT;
15851 snprintf(valueBuf, sizeof(valueBuf), "%d INT", value);
15852 break;
15853 case SHEET_PER:
15854 value = stats[player.playernum]->PER;
15855 snprintf(valueBuf, sizeof(valueBuf), "%d PER", value);
15856 break;
15857 case SHEET_CHR:
15858 value = stats[player.playernum]->CHR;
15859 snprintf(valueBuf, sizeof(valueBuf), "%d CHR", value);
15860 break;
15861 default:
15862 break;
15863 }
15864 entryValue->setColor(hudColors.characterSheetNeutral);
15865 if ( value < 0 )
15866 {
15867 entryValue->setColor(hudColors.characterSheetRed);
15868 }
15869 entryValue->setText(valueBuf);
15870 SDL_Rect entryValuePos = entry->getSize();
15871 entryValue->setSize(entryValuePos);
15872 entryValue->setHJustify(Frame::justify_t::RIGHT);
15873 entryValue->setVJustify(Field::justify_t::TOP);
15874
15875 /*auto txtValueBackingFrame = tooltipFrame->findFrame("txt value backing frame 1");
15876 SDL_Rect backingFramePos = entryValue->getSize();
15877 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
15878 entryValue->getTextColor(), entryValue->getOutlineColor());
15879 longestValue = std::max(longestValue, txtValueGet->getWidth());
15880 backingFramePos.x = backingFramePos.x + backingFramePos.w;
15881 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
15882 valueSizes[1] = std::make_pair(entryValue, backingFramePos);
15883 txtValueBackingFrame->setDisabled(false);*/
15884 }
15885 {
15886 currentHeight += 0;// padyMid / 2;
15887 auto entry = characterSheetTooltipTextFields[player.playernum][3]; assert(entry)(static_cast<void> (0));
15888 entry->setDisabled(false);
15889 char buf[128] = "";
15890 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_modified_amount").c_str());
15891 entry->setText(buf);
15892 entry->setVJustify(Field::justify_t::TOP);
15893
15894 auto glyphBacking = tooltipFrame->findImage("glyph 1");
15895
15896 SDL_Rect entryPos = entry->getSize();
15897 entryPos.x = padx / 2 + glyphBacking->pos.x + glyphBacking->pos.w;
15898 entryPos.y = currentHeight;
15899 entryPos.w = txtPos.w - (padxMid + glyphBacking->pos.x + glyphBacking->pos.w);
15900 entry->setSize(entryPos);
15901 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry3 != entry->getText() )
15902 {
15903 entry->reflowTextToFit(0);
15904 charsheetTooltipCache[player.playernum].textEntries[element].entry3 = entry->getText();
15905 }
15906 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
15907 entry->setSize(entryPos);
15908 entry->setColor(defaultColor);
15909 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
15910 tooltipPos.h = pady1 + currentHeight + pady2;
15911
15912 auto entryValue = characterSheetTooltipTextFields[player.playernum][4]; assert(entry)(static_cast<void> (0));
15913 entryValue->setDisabled(false);
15914 char valueBuf[128] = "";
15915 int value = 0;
15916 switch ( element )
15917 {
15918 case SHEET_STR:
15919 value = statGetSTR(stats[player.playernum], players[player.playernum]->entity);
15920 value -= stats[player.playernum]->STR;
15921 snprintf(valueBuf, sizeof(valueBuf), "%+d STR", value);
15922 break;
15923 case SHEET_DEX:
15924 value = statGetDEX(stats[player.playernum], players[player.playernum]->entity);
15925 value -= stats[player.playernum]->DEX;
15926 snprintf(valueBuf, sizeof(valueBuf), "%+d DEX", value);
15927 break;
15928 case SHEET_CON:
15929 value = statGetCON(stats[player.playernum], players[player.playernum]->entity);
15930 value -= stats[player.playernum]->CON;
15931 snprintf(valueBuf, sizeof(valueBuf), "%+d CON", value);
15932 break;
15933 case SHEET_INT:
15934 value = statGetINT(stats[player.playernum], players[player.playernum]->entity);
15935 value -= stats[player.playernum]->INT;
15936 snprintf(valueBuf, sizeof(valueBuf), "%+d INT", value);
15937 break;
15938 case SHEET_PER:
15939 value = statGetPER(stats[player.playernum], players[player.playernum]->entity);
15940 value -= stats[player.playernum]->PER;
15941 snprintf(valueBuf, sizeof(valueBuf), "%+d PER", value);
15942 break;
15943 case SHEET_CHR:
15944 value = statGetCHR(stats[player.playernum], players[player.playernum]->entity);
15945 value -= stats[player.playernum]->CHR;
15946 snprintf(valueBuf, sizeof(valueBuf), "%+d CHR", value);
15947 break;
15948 default:
15949 break;
15950 }
15951 entryValue->setColor(hudColors.characterSheetNeutral);
15952 if ( value < 0 )
15953 {
15954 entryValue->setColor(hudColors.characterSheetRed);
15955 }
15956 else if ( value > 0 )
15957 {
15958 entryValue->setColor(hudColors.characterSheetGreen);
15959 }
15960 entryValue->setText(valueBuf);
15961 SDL_Rect entryValuePos = entry->getSize();
15962 entryValue->setSize(entryValuePos);
15963 entryValue->setHJustify(Frame::justify_t::RIGHT);
15964 entryValue->setVJustify(Field::justify_t::TOP);
15965
15966 /*auto txtValueBackingFrame = tooltipFrame->findFrame("txt value backing frame 2");
15967 SDL_Rect backingFramePos = entryValue->getSize();
15968 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
15969 entryValue->getTextColor(), entryValue->getOutlineColor());
15970 longestValue = std::max(longestValue, txtValueGet->getWidth());
15971 backingFramePos.x = backingFramePos.x + backingFramePos.w;
15972 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
15973 valueSizes[2] = std::make_pair(entryValue, backingFramePos);
15974 txtValueBackingFrame->setDisabled(false);*/
15975 }
15976 {
15977 // stat extra number display
15978 currentHeight += padyMid;
15979 auto entry = characterSheetTooltipTextFields[player.playernum][5]; assert(entry)(static_cast<void> (0));
15980 entry->setDisabled(false);
15981 char buf[128] = "";
15982 if ( element == SHEET_STR )
15983 {
15984 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_str_atk_bonus").c_str());
15985 }
15986 else if ( element == SHEET_DEX )
15987 {
15988 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_dex_ranged_atk_bonus").c_str());
15989 }
15990 else if ( element == SHEET_CON )
15991 {
15992 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_con_ac_bonus").c_str());
15993 }
15994 else if ( element == SHEET_PER )
15995 {
15996 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_per_pierce_bonus").c_str());
15997 }
15998 else if ( element == SHEET_INT )
15999 {
16000 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_int_pwr_bonus").c_str());
16001 }
16002 else if ( element == SHEET_CHR )
16003 {
16004 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_chr_buying_bonus").c_str());
16005 }
16006 entry->setText(buf);
16007 entry->setVJustify(Field::justify_t::TOP);
16008
16009 SDL_Rect entryPos = entry->getSize();
16010 entryPos.x = padx + padxMid;
16011 entryPos.y = currentHeight;
16012 entryPos.w = txtPos.w - (padxMid * 2);
16013 entry->setSize(entryPos);
16014 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry5 != entry->getText() )
16015 {
16016 entry->reflowTextToFit(0);
16017 charsheetTooltipCache[player.playernum].textEntries[element].entry5 = entry->getText();
16018 }
16019 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
16020 entry->setSize(entryPos);
16021 entry->setColor(defaultColor);
16022 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
16023 tooltipPos.h = pady1 + currentHeight + pady2;
16024
16025 auto entryValue = characterSheetTooltipTextFields[player.playernum][6]; assert(entry)(static_cast<void> (0));
16026 entryValue->setDisabled(false);
16027 char valueBuf[128] = "";
16028 int value = 0;
16029 switch ( element )
16030 {
16031 case SHEET_STR:
16032 {
16033 Sint32 STR = statGetSTR(stats[player.playernum], players[player.playernum]->entity);
16034 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_atk_value_format").c_str(), STR);
16035 }
16036 break;
16037 case SHEET_DEX:
16038 {
16039 Sint32 DEX = statGetDEX(stats[player.playernum], players[player.playernum]->entity);
16040 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_atk_value_format").c_str(), DEX);
16041 }
16042 break;
16043 case SHEET_CON:
16044 {
16045 Sint32 CON = statGetCON(stats[player.playernum], players[player.playernum]->entity);
16046 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_ac_value_format").c_str(), CON);
16047 }
16048 break;
16049 case SHEET_INT:
16050 {
16051 //real_t val = getBonusFromCasterOfSpellElement(players[player.playernum]->entity, stats[player.playernum], nullptr, SPELL_NONE) * 100.0;
16052 int INT = statGetINT(stats[player.playernum], players[player.playernum]->entity);
16053 real_t bonus = 0.0;
16054 if ( INT > 0 )
16055 {
16056 bonus += INT / 100.0;
16057 }
16058 real_t val = bonus * 100.0;
16059 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_pwr_value_format").c_str(), val);
16060 }
16061 break;
16062 case SHEET_PER:
16063 {
16064 real_t val = std::min(std::max(statGetPER(stats[player.playernum], players[player.playernum]->entity) / 2, 0), 50);
16065 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_pierce_value_format").c_str(), val);
16066 }
16067 break;
16068 case SHEET_CHR:
16069 {
16070 real_t val = 1 / ((50 + stats[player.playernum]->getModifiedProficiency(PRO_TRADING)) / 150.f); // buy value
16071 real_t normalVal = val;
16072 //normalVal /= (1.f + statGetCHR(stats[player.playernum], players[player.playernum]->entity) / 20.f);
16073 normalVal = std::max(1.0, normalVal);
16074
16075 int stat = stats[player.playernum]->CHR;
16076 stats[player.playernum]->CHR = 0;
16077 stats[player.playernum]->CHR -= statGetCHR(stats[player.playernum], players[player.playernum]->entity);
16078 real_t zeroVal = val;
16079 //zeroVal /= (1.f + statGetCHR(stats[player.playernum], players[player.playernum]->entity) / 20.f);
16080 zeroVal = std::max(1.0, zeroVal);
16081 stats[player.playernum]->CHR = stat;
16082
16083 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_buying_value_format").c_str(), (zeroVal - normalVal) * 100.0);
16084 }
16085 break;
16086 default:
16087 break;
16088 }
16089 entryValue->setColor(hudColors.characterSheetNeutral);
16090 if ( value < 0 )
16091 {
16092 entryValue->setColor(hudColors.characterSheetRed);
16093 }
16094 else if ( value > 0 )
16095 {
16096 entryValue->setColor(hudColors.characterSheetGreen);
16097 }
16098 entryValue->setText(valueBuf);
16099 entryValue->setSize(entry->getSize());
16100 entryValue->setHJustify(Frame::justify_t::LEFT);
16101 entryValue->setVJustify(Field::justify_t::TOP);
16102
16103 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][3];
16104 SDL_Rect backingFramePos = entryValue->getSize();
16105 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
16106 entryValue->getTextColor(), entryValue->getOutlineColor());
16107 longestValue = std::max(longestValue, txtValueGet->getWidth());
16108 backingFramePos.x = backingFramePos.x + backingFramePos.w;
16109 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
16110 valueSizes[3] = std::make_pair(entryValue, backingFramePos);
16111 txtValueBackingFrame->setDisabled(false);
16112 }
16113 if ( element == SHEET_STR || element == SHEET_DEX || element == SHEET_INT || element == SHEET_PER || element == SHEET_CHR )
16114 {
16115 // stat extra number display
16116 currentHeight += padyMid;
16117 auto entry = characterSheetTooltipTextFields[player.playernum][7]; assert(entry)(static_cast<void> (0));
16118 entry->setDisabled(false);
16119 char buf[128] = "";
16120
16121 if ( element == SHEET_STR )
16122 {
16123 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_str_movement_bonus").c_str());
16124 }
16125 else if ( element == SHEET_DEX )
16126 {
16127 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_dex_thrown_atk_bonus").c_str());
16128 }
16129 else if ( element == SHEET_INT )
16130 {
16131 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_int_mp_regen_bonus").c_str());
16132 }
16133 else if ( element == SHEET_PER )
16134 {
16135 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_per_light_bonus").c_str());
16136 }
16137 else if ( element == SHEET_CHR )
16138 {
16139 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_chr_selling_bonus").c_str());
16140 }
16141 entry->setText(buf);
16142 entry->setVJustify(Field::justify_t::TOP);
16143
16144 SDL_Rect entryPos = entry->getSize();
16145 entryPos.x = padx + padxMid;
16146 entryPos.y = currentHeight;
16147 entryPos.w = txtPos.w - (padxMid * 2);
16148 entry->setSize(entryPos);
16149 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry7 != entry->getText() )
16150 {
16151 entry->reflowTextToFit(0);
16152 charsheetTooltipCache[player.playernum].textEntries[element].entry7 = entry->getText();
16153 }
16154 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
16155 entry->setSize(entryPos);
16156 entry->setColor(defaultColor);
16157 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
16158 tooltipPos.h = pady1 + currentHeight + pady2;
16159
16160 auto entryValue = characterSheetTooltipTextFields[player.playernum][8]; assert(entry)(static_cast<void> (0));
16161 entryValue->setDisabled(false);
16162 char valueBuf[128] = "";
16163 int value = 0;
16164 switch ( element )
16165 {
16166 case SHEET_STR:
16167 {
16168 Sint32 STR = statGetSTR(stats[player.playernum], players[player.playernum]->entity);
16169 Sint32 DEX = statGetDEX(stats[player.playernum], players[player.playernum]->entity);
16170 real_t weightratio1 = player.movement.getWeightRatio(player.movement.getCharacterModifiedWeight(), 0);
16171 real_t weightratio2 = player.movement.getWeightRatio(player.movement.getCharacterModifiedWeight(), STR);
16172 real_t speedFactor1 = player.movement.getSpeedFactor(weightratio1, DEX);
16173 real_t speedFactor2 = player.movement.getSpeedFactor(weightratio2, DEX);
16174 real_t maxSpeed = player.movement.getMaximumSpeed();
16175
16176 real_t noSTRPercent = 100.0 * speedFactor1 / std::fmax(.01, maxSpeed);
16177 real_t currentPercent = 100.0 * speedFactor2 / std::fmax(.01, maxSpeed);
16178 real_t displayValue = currentPercent - noSTRPercent;
16179 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_movement_value_format").c_str(), displayValue);
16180 }
16181 break;
16182 case SHEET_DEX:
16183 {
16184 Sint32 DEX = statGetDEX(stats[player.playernum], players[player.playernum]->entity);
16185 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_atk_value_format").c_str(), DEX / 4);
16186 }
16187 break;
16188 case SHEET_CON:
16189 break;
16190 case SHEET_INT:
16191 {
16192 Sint32 oldINT = stats[player.playernum]->INT;
16193 stats[player.playernum]->INT += -statGetINT(stats[player.playernum], player.entity);
16194 real_t regenWithoutINT = getDisplayedMPRegen(player.entity, *stats[player.playernum], nullptr, nullptr);
16195 stats[player.playernum]->INT = oldINT;
16196
16197 real_t regenTotal = getDisplayedMPRegen(player.entity, *stats[player.playernum], nullptr, nullptr);
16198 real_t regenStatSkill = regenTotal - regenWithoutINT;
16199 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_mp_regen_value_format").c_str(), regenStatSkill);
16200 }
16201 break;
16202 case SHEET_PER:
16203 {
16204 const int PER = statGetPER(stats[player.playernum], players[player.playernum]->entity);
16205 const int range_bonus = std::min(std::max(0, PER / 5), 2);
16206 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_light_value_format").c_str(), range_bonus);
16207 }
16208 break;
16209 case SHEET_CHR:
16210 {
16211 real_t val = (50 + stats[player.playernum]->getModifiedProficiency(PRO_TRADING)) / 150.f; // sell value
16212 real_t normalVal = val;
16213 normalVal *= (1.f + statGetCHR(stats[player.playernum], players[player.playernum]->entity) / 20.f);
16214 normalVal = std::min(1.0, normalVal);
16215
16216 int stat = stats[player.playernum]->CHR;
16217 stats[player.playernum]->CHR = 0;
16218 stats[player.playernum]->CHR -= statGetCHR(stats[player.playernum], players[player.playernum]->entity);
16219 real_t zeroVal = val;
16220 zeroVal *= (1.f + statGetCHR(stats[player.playernum], players[player.playernum]->entity) / 20.f);
16221 zeroVal = std::min(1.0, zeroVal);
16222 stats[player.playernum]->CHR = stat;
16223
16224 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_selling_value_format").c_str(), (normalVal - zeroVal) * 100.0);
16225 }
16226 break;
16227 default:
16228 break;
16229 }
16230 entryValue->setColor(hudColors.characterSheetNeutral);
16231 if ( value < 0 )
16232 {
16233 entryValue->setColor(hudColors.characterSheetRed);
16234 }
16235 else if ( value > 0 )
16236 {
16237 entryValue->setColor(hudColors.characterSheetGreen);
16238 }
16239 entryValue->setText(valueBuf);
16240 entryValue->setSize(entry->getSize());
16241 entryValue->setHJustify(Frame::justify_t::LEFT);
16242 entryValue->setVJustify(Field::justify_t::TOP);
16243
16244 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][4];
16245 SDL_Rect backingFramePos = entryValue->getSize();
16246 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
16247 entryValue->getTextColor(), entryValue->getOutlineColor());
16248 longestValue = std::max(longestValue, txtValueGet->getWidth());
16249 backingFramePos.x = backingFramePos.x + backingFramePos.w;
16250 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
16251 valueSizes[4] = std::make_pair(entryValue, backingFramePos);
16252 txtValueBackingFrame->setDisabled(false);
16253 }
16254 if ( element == SHEET_PER || element == SHEET_DEX )
16255 {
16256 // stat extra number display
16257 currentHeight += padyMid;
16258 auto entry = characterSheetTooltipTextFields[player.playernum][9]; assert(entry)(static_cast<void> (0));
16259 entry->setDisabled(false);
16260 char buf[128] = "";
16261 if ( element == SHEET_DEX )
16262 {
16263 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_dex_movement_bonus").c_str());
16264 }
16265 else if ( element == SHEET_PER )
16266 {
16267 snprintf(buf, sizeof(buf), "%s", getHoverTextString("stat_per_sneaking_bonus").c_str());
16268 }
16269 entry->setText(buf);
16270 entry->setVJustify(Field::justify_t::TOP);
16271
16272 SDL_Rect entryPos = entry->getSize();
16273 entryPos.x = padx + padxMid;
16274 entryPos.y = currentHeight;
16275 entryPos.w = txtPos.w - (padxMid * 2);
16276 entry->setSize(entryPos);
16277 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry9 != entry->getText() )
16278 {
16279 entry->reflowTextToFit(0);
16280 charsheetTooltipCache[player.playernum].textEntries[element].entry9 = entry->getText();
16281 }
16282 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
16283 entry->setSize(entryPos);
16284 entry->setColor(defaultColor);
16285 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
16286 tooltipPos.h = pady1 + currentHeight + pady2;
16287
16288 auto entryValue = characterSheetTooltipTextFields[player.playernum][10]; assert(entry)(static_cast<void> (0));
16289 entryValue->setDisabled(false);
16290 char valueBuf[128] = "";
16291 int value = 0;
16292 switch ( element )
16293 {
16294 case SHEET_STR:
16295 break;
16296 case SHEET_DEX:
16297 {
16298 Sint32 STR = statGetSTR(stats[player.playernum], players[player.playernum]->entity);
16299 real_t weightratio = player.movement.getWeightRatio(player.movement.getCharacterModifiedWeight(), STR);
16300 Sint32 DEX = statGetDEX(stats[player.playernum], players[player.playernum]->entity);
16301 real_t speedFactor1 = player.movement.getSpeedFactor(weightratio, 0);
16302 real_t speedFactor2 = player.movement.getSpeedFactor(weightratio, DEX);
16303 real_t maxSpeed = player.movement.getMaximumSpeed();
16304
16305 real_t noDEXPercent = 100.0 * speedFactor1 / std::fmax(.01, maxSpeed);
16306 real_t currentPercent = 100.0 * speedFactor2 / std::fmax(.01, maxSpeed);
16307
16308 real_t displayValue = currentPercent - noDEXPercent;
16309 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_movement_value_format").c_str(), displayValue);
16310 }
16311 break;
16312 case SHEET_CON:
16313 break;
16314 case SHEET_INT:
16315 break;
16316 case SHEET_PER:
16317 {
16318 const int PER = statGetPER(stats[player.playernum], players[player.playernum]->entity);
16319 const int range_bonus = std::min(std::max(0, PER / 5), 2);
16320 const int sneakingBonus = range_bonus + 2;
16321 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("stat_light_value_format").c_str(), sneakingBonus);
16322 }
16323 break;
16324 case SHEET_CHR:
16325 break;
16326 default:
16327 break;
16328 }
16329 entryValue->setColor(hudColors.characterSheetNeutral);
16330 if ( value < 0 )
16331 {
16332 entryValue->setColor(hudColors.characterSheetRed);
16333 }
16334 else if ( value > 0 )
16335 {
16336 entryValue->setColor(hudColors.characterSheetGreen);
16337 }
16338 entryValue->setText(valueBuf);
16339 entryValue->setSize(entry->getSize());
16340 entryValue->setHJustify(Frame::justify_t::LEFT);
16341 entryValue->setVJustify(Field::justify_t::TOP);
16342
16343 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][5];
16344 SDL_Rect backingFramePos = entryValue->getSize();
16345 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
16346 entryValue->getTextColor(), entryValue->getOutlineColor());
16347 longestValue = std::max(longestValue, txtValueGet->getWidth());
16348 backingFramePos.x = backingFramePos.x + backingFramePos.w;
16349 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
16350 valueSizes[5] = std::make_pair(entryValue, backingFramePos);
16351 txtValueBackingFrame->setDisabled(false);
16352 }
16353
16354 for ( int index = 1; index <= NUM_CHARSHEET_TOOLTIP_BACKING_FRAMES; ++index )
16355 {
16356 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][index];
16357 if ( txtValueBackingFrame->isDisabled() )
16358 {
16359 continue;
16360 }
16361 if ( valueSizes.find(index) == valueSizes.end() )
16362 {
16363 continue;
16364 }
16365
16366 SDL_Rect valuePos = valueSizes[index].second;
16367 Field* entryValue = valueSizes[index].first;
16368 SDL_Rect entryValuePos = entryValue->getSize();
16369 entryValuePos.x = entryValuePos.x + entryValuePos.w;
16370 entryValuePos.w = (int)longestValue;
16371 entryValuePos.x -= entryValuePos.w;
16372 entryValuePos.x -= 8;
16373 entryValue->setSize(entryValuePos);
16374
16375 valuePos.w = (int)longestValue + 16;
16376 valuePos.x -= (valuePos.w);
16377 valuePos.y -= 3;
16378 valuePos.h += 4;
16379
16380 txtValueBackingFrame->setSize(valuePos);
16381
16382 imageResizeToContainer9x9(txtValueBackingFrame, SDL_Rect{ 0, 0, valuePos.w, valuePos.h }, skillsheetEffectBackgroundImages);
16383 }
16384
16385 {
16386 currentHeight += padyMid;
16387
16388 div->pos.x = padx;
16389 div->pos.y = currentHeight;
16390 div->pos.w = txtPos.w;
16391 div->disabled = false;
16392
16393 currentHeight += padyMid;
16394
16395 auto entry = characterSheetTooltipTextFields[player.playernum][11]; assert(entry)(static_cast<void> (0));
16396 entry->setDisabled(false);
16397 char buf[512] = "";
16398
16399 std::string descTextFormatted = "\x1E ";
16400 for ( auto s : descText )
16401 {
16402 descTextFormatted += s;
16403 if ( s == '\n' )
16404 {
16405 descTextFormatted += "\x1E ";
16406 }
16407 }
16408
16409 snprintf(buf, sizeof(buf), "%s", descTextFormatted.c_str());
16410 entry->setText(buf);
16411
16412 SDL_Rect entryPos = entry->getSize();
16413 entryPos.x = padx;
16414 entryPos.y = currentHeight;
16415 entryPos.w = txtPos.w;
16416 entry->setSize(entryPos);
16417 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry11 != entry->getText() )
16418 {
16419 entry->reflowTextToFit(0);
16420 charsheetTooltipCache[player.playernum].textEntries[element].entry11 = entry->getText();
16421 }
16422 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
16423 entry->setSize(entryPos);
16424 entry->setColor(hudColors.characterSheetOffWhiteText);
16425 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
16426
16427 currentHeight += padyMid / 4;
16428 tooltipPos.h = pady1 + currentHeight + pady2;
16429 }
16430
16431 tooltipPos.h = pady1 + currentHeight + pady2;
16432 if ( tooltipJustify == PANEL_JUSTIFY_RIGHT )
16433 {
16434 tooltipPos.x = pos.x - tooltipPos.w;
16435 }
16436 else
16437 {
16438 tooltipPos.x = pos.x;
16439 }
16440 tooltipPos.y = pos.y;
16441
16442 tooltipFrame->setSize(tooltipPos);
16443 imageResizeToContainer9x9(tooltipFrame, SDL_Rect{ 0, 0, tooltipPos.w, tooltipPos.h },
16444 skillsheetEffectBackgroundImages);
16445 }
16446 else if ( element >= Player::CharacterSheet_t::SHEET_ATK && element <= Player::CharacterSheet_t::SHEET_WGT )
16447 {
16448 AttackHoverText_t attackHoverTextInfo;
16449 Sint32 attackPower = displayAttackPower(player.playernum, attackHoverTextInfo);
16450
16451//#ifndef NDEBUG
16452// if ( keystatus[SDLK_V] )
16453// {
16454// keystatus[SDLK_V] = 0;
16455// messagePlayer(player.playernum, MESSAGE_DEBUG, "Remove this");
16456// stats[player.playernum]->playerRace = RACE_AUTOMATON;
16457// stats[player.playernum]->appearance = 0;
16458// }
16459// if ( keystatus[SDLK_B] )
16460// {
16461// keystatus[SDLK_B] = 0;
16462// messagePlayer(player.playernum, MESSAGE_DEBUG, "Remove this");
16463// stats[player.playernum]->playerRace = RACE_INSECTOID;
16464// stats[player.playernum]->appearance = 0;
16465// }
16466//#endif // !NDEBUG
16467
16468 bool isAutomatonHTRegen = stats[player.playernum]->type == AUTOMATON;
16469 bool isInsectoidENRegen = (stats[player.playernum]->playerRace == RACE_INSECTOID && stats[player.playernum]->appearance == 0);
16470
16471 auto tooltipTopLeft = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
16472 tooltipTopLeft->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TL_Blue_00.png";
16473 auto tooltipTop = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP].c_str());
16474 tooltipTop->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_T_Blue_00.png";
16475 auto tooltipTopRight = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
16476 tooltipTopRight->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TR_Blue_00.png";
16477 imageSetWidthHeight9x9(tooltipFrame, skillsheetEffectBackgroundImages);
16478
16479 int maxWidth = 260;
16480 if ( getHoverTextString("attributes_max_tooltip_width") != defaultString )
16481 {
16482 maxWidth = std::max(0, std::stoi(getHoverTextString("attributes_max_tooltip_width")));
16483 }
16484 int minWidth = 0;
16485 if ( getHoverTextString("attributes_min_tooltip_width") != defaultString )
16486 {
16487 minWidth = std::max(0, std::stoi(getHoverTextString("attributes_min_tooltip_width")));
16488 }
16489 const int padx = 16;
16490 const int pady1 = 8;
16491 const int pady2 = 4;
16492 const int padxMid = 4;
16493 const int padyMid = 8;
16494 SDL_Rect tooltipPos = SDL_Rect{ 400, 0, maxWidth, 100 };
16495
16496 std::string titleText = "";
16497 std::string descText = "";
16498 int value = 0;
16499 switch ( element )
16500 {
16501 case SHEET_ATK:
16502 {
16503 char descBuf[256];
16504 std::string skillName = "-";
16505 for ( auto& skill : player.skillSheet.skillSheetData.skillEntries )
16506 {
16507 if ( skill.skillId == attackHoverTextInfo.proficiency )
16508 {
16509 skillName = skill.name;
16510 break;
16511 }
16512 }
16513 titleText = getHoverTextString("attributes_atk_title");
16514 txtRightAlignHint->setText("");
16515 txtRightAlignHint->setDisabled(false);
16516 if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_UNARMED )
16517 {
16518 txtRightAlignHint->setText(getHoverTextString("attributes_atk_title_unarmed").c_str());
16519 }
16520 else if ( stats[player.playernum]->weapon )
16521 {
16522 Item* item = stats[player.playernum]->weapon;
16523 if ( item->type >= WOODEN_SHIELD && item->type < NUMITEMS )
16524 {
16525 char itemNameBuf[128];
16526 if ( itemCategory(item) == MAGICSTAFF )
16527 {
16528 snprintf(itemNameBuf, sizeof(itemNameBuf), "%s (%+d)", item->getName(), item->beatitude);
16529 std::string itemNameStr = itemNameBuf;
16530 capitalizeString(itemNameStr);
16531 txtRightAlignHint->setText(itemNameStr.c_str());
16532 }
16533 else
16534 {
16535 snprintf(itemNameBuf, sizeof(itemNameBuf), "%s %s (%+d)",
16536 ItemTooltips.getItemStatusAdjective(item->type, item->status).c_str(), item->getName(), item->beatitude);
16537 txtRightAlignHint->setText(itemNameBuf);
16538 }
16539 }
16540 }
16541 if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_MELEE_WEAPON
16542 || attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_WHIP )
16543 {
16544 snprintf(descBuf, sizeof(descBuf), getHoverTextString("attributes_atk_melee_desc").c_str(), skillName.c_str());
16545 descText = descBuf;
16546 }
16547 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_UNARMED )
16548 {
16549 snprintf(descBuf, sizeof(descBuf), getHoverTextString("attributes_atk_unarmed_desc").c_str(), skillName.c_str());
16550 descText = descBuf;
16551 }
16552 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_RANGED )
16553 {
16554 snprintf(descBuf, sizeof(descBuf), getHoverTextString("attributes_atk_ranged_desc").c_str(), skillName.c_str());
16555 descText = descBuf;
16556 }
16557 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_THROWN )
16558 {
16559 snprintf(descBuf, sizeof(descBuf), getHoverTextString("attributes_atk_thrown_desc").c_str(), skillName.c_str());
16560 descText = descBuf;
16561 }
16562 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_THROWN_GEM )
16563 {
16564 snprintf(descBuf, sizeof(descBuf), getHoverTextString("attributes_atk_gem_desc").c_str(), skillName.c_str());
16565 descText = descBuf;
16566 }
16567 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_THROWN_POTION )
16568 {
16569 snprintf(descBuf, sizeof(descBuf), getHoverTextString("attributes_atk_potion_desc").c_str(), skillName.c_str());
16570 descText = descBuf;
16571 }
16572 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_PICKAXE )
16573 {
16574 snprintf(descBuf, sizeof(descBuf), getHoverTextString("attributes_atk_pickaxe_desc").c_str());
16575 descText = descBuf;
16576 }
16577 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_MAGICSTAFF )
16578 {
16579 snprintf(descBuf, sizeof(descBuf), getHoverTextString("attributes_atk_magicstaff_desc").c_str());
16580 descText = descBuf;
16581 }
16582 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_TOOL )
16583 {
16584 snprintf(descBuf, sizeof(descBuf), getHoverTextString("attributes_atk_tool_desc").c_str());
16585 descText = descBuf;
16586 }
16587 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_TOOL_TRAP )
16588 {
16589 snprintf(descBuf, sizeof(descBuf), getHoverTextString("attributes_atk_tinker_tool_desc").c_str());
16590 descText = descBuf;
16591 }
16592 else
16593 {
16594 descText = "";
16595 }
16596 }
16597 break;
16598 case SHEET_AC:
16599 titleText = getHoverTextString("attributes_ac_title");
16600 descText = getHoverTextString("attributes_ac_desc");
16601 break;
16602 case SHEET_POW:
16603 titleText = getHoverTextString("attributes_pwr_title");
16604 descText = getHoverTextString("attributes_pwr_desc");
16605 break;
16606 case SHEET_RES:
16607 titleText = getHoverTextString("attributes_res_title");
16608 descText = getHoverTextString("attributes_res_desc");
16609 break;
16610 case SHEET_RGN:
16611 titleText = getHoverTextString("attributes_rgn_hp_title");
16612 if ( !(svFlags & SV_FLAG_HUNGER) )
16613 {
16614 descText = getHoverTextString("attributes_rgn_hp_desc_no_hunger");
16615 }
16616 else
16617 {
16618 descText = getHoverTextString("attributes_rgn_hp_desc");
16619 }
16620 break;
16621 case SHEET_RGN_MP:
16622 if ( isAutomatonHTRegen )
16623 {
16624 titleText = getHoverTextString("attributes_rgn_ht_title");
16625 descText = getHoverTextString("attributes_rgn_ht_desc");
16626 }
16627 else if ( isInsectoidENRegen )
16628 {
16629 titleText = getHoverTextString("attributes_rgn_en_title");
16630 if ( !(svFlags & SV_FLAG_HUNGER) )
16631 {
16632 descText = getHoverTextString("attributes_rgn_en_desc_no_hunger");
16633 }
16634 else
16635 {
16636 descText = getHoverTextString("attributes_rgn_en_desc");
16637 }
16638 }
16639 else
16640 {
16641 titleText = getHoverTextString("attributes_rgn_mp_title");
16642 descText = getHoverTextString("attributes_rgn_mp_desc");
16643 }
16644 break;
16645 case SHEET_WGT:
16646 titleText = getHoverTextString("attributes_wgt_title");
16647 descText = getHoverTextString("attributes_wgt_desc");
16648 break;
16649 default:
16650 break;
16651 }
16652
16653 txt->setText(titleText.c_str());
16654 SDL_Rect txtPos = SDL_Rect{ padx, pady1 - 2, maxWidth - padx * 2, 80 };
16655 txt->setSize(txtPos);
16656 if ( charsheetTooltipCache[player.playernum].textEntries[element].title != txt->getText() )
16657 {
16658 txt->reflowTextToFit(0);
16659 charsheetTooltipCache[player.playernum].textEntries[element].title = txt->getText();
16660 }
16661 txt->setColor(hudColors.characterSheetHeadingText);
16662 Font* actualFont = Font::get(txt->getFont());
16663 int txtHeight = txt->getNumTextLines() * actualFont->height(true);
16664 txtPos.h = txtHeight + 4;
16665 auto txtGet = Text::get(txt->getLongestLine().c_str(), txt->getFont(),
16666 txt->getTextColor(), txt->getOutlineColor());
16667 txtPos.w = txtGet->getWidth();
16668 txtPos.w = std::max(minWidth - padx * 2, txtPos.w);
16669 txt->setSize(txtPos);
16670
16671 txtRightAlignHint->setSize(txtPos);
16672 txtRightAlignHint->setHJustify(Field::justify_t::RIGHT);
16673
16674 tooltipPos.w = (txtPos.w + padx * 2);
16675
16676 unsigned int longestValue = 0;
16677 std::map<int, std::pair<Field*, SDL_Rect>> valueSizes;
16678
16679 int currentHeight = txtPos.y + (actualFont->height(true) * 1) + 2;
16680 const int extraTextHeightForLowerCharacters = 4;
16681 int currentTextFieldIndex = 1;
16682 int currentTextBackingFrameIndex = 1;
16683
16684 char buf[128] = "";
16685 char valueBuf[128] = "";
16686
16687 if ( element == SHEET_ATK && getAttackTooltipLines(player.playernum, attackHoverTextInfo, 1, buf, valueBuf)
16688 || element != SHEET_ATK )
16689 {
16690 currentHeight += padyMid;
16691 auto entry = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
16692 ++currentTextFieldIndex;
16693 entry->setDisabled(false);
16694 switch ( element )
16695 {
16696 case SHEET_ATK:
16697 break;
16698 case SHEET_AC:
16699 {
16700 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_ac_base").c_str());
16701 bool oldDefending = stats[player.playernum]->defending;
16702 stats[player.playernum]->defending = false;
16703 Sint32 armor = AC(stats[player.playernum]);
16704 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_ac_nobonus_format").c_str(), armor);
16705 stats[player.playernum]->defending = oldDefending;
16706 }
16707 break;
16708 case SHEET_POW:
16709 {
16710 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_pwr_base").c_str());
16711 std::string tag = "MAGIC_SPELLPOWER_TOTAL";
16712 std::string formatValue = "%d";
16713 std::string pwrBonus = formatSkillSheetEffects(player.playernum, PRO_MAGIC, tag, formatValue);
16714 Sint32 pwr = 100 + std::stoi(pwrBonus);
16715 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_pwr_nobonus_format").c_str(), pwr);
16716 }
16717 break;
16718 case SHEET_RES:
16719 {
16720 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_res_base").c_str());
16721 real_t resistance = 100.0 * Entity::getDamageTableMultiplier(player.entity, *stats[player.playernum], DAMAGE_TABLE_MAGIC);
16722 resistance /= (Entity::getMagicResistance(stats[player.playernum]) + 1);
16723 resistance = 100.0 - resistance;
16724 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_res_nobonus_format").c_str(), (int)resistance);
16725 }
16726 break;
16727 case SHEET_RGN:
16728 {
16729 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_hp_base").c_str());
16730 char hpbuf[32] = "";
16731 getDisplayedHPRegen(players[player.playernum]->entity, *stats[player.playernum], nullptr, hpbuf);
16732 if ( !(svFlags & SV_FLAG_HUNGER) )
16733 {
16734 snprintf(valueBuf, sizeof(valueBuf), "%s", hpbuf);
16735 }
16736 else
16737 {
16738 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_nobonus_format").c_str(), hpbuf);
16739 }
16740 }
16741 break;
16742 case SHEET_RGN_MP:
16743 {
16744 char mpbuf[32] = "";
16745 real_t regen = getDisplayedMPRegen(players[player.playernum]->entity, *stats[player.playernum], nullptr, mpbuf);
16746 if ( isAutomatonHTRegen )
16747 {
16748 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_ht_base").c_str());
16749 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_nobonus_format").c_str(), mpbuf);
16750 }
16751 else if ( isInsectoidENRegen )
16752 {
16753 if ( !(svFlags & SV_FLAG_HUNGER) )
16754 {
16755 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_en_base").c_str());
16756 snprintf(valueBuf, sizeof(valueBuf), "%s", mpbuf);
16757 }
16758 else
16759 {
16760 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_en_base").c_str());
16761 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_en_nobonus_format").c_str(), mpbuf);
16762 }
16763 }
16764 else
16765 {
16766 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_mp_base").c_str());
16767 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_nobonus_format").c_str(), mpbuf);
16768 }
16769 }
16770 break;
16771 case SHEET_WGT:
16772 {
16773 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_wgt_base").c_str());
16774 int weight = player.movement.getCharacterWeight();
16775 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_wgt_nobonus_format").c_str(), weight);
16776 }
16777 break;
16778 default:
16779 break;
16780 }
16781 entry->setText(buf);
16782 entry->setVJustify(Field::justify_t::TOP);
16783
16784 auto glyphBacking = tooltipFrame->findImage("glyph 1");
16785 glyphBacking->disabled = false;
16786 glyphBacking->path = getHoverTextString("icon_backing_path");
16787 glyphBacking->pos.x = padx + padxMid + 4;
16788 glyphBacking->pos.y = currentHeight + 6;
16789 glyphBacking->pos.w = 44;
16790 glyphBacking->pos.h = 44;
16791
16792 auto glyphIcon = tooltipFrame->findImage("glyph 2");
16793 glyphIcon->disabled = false;
16794 glyphIcon->path = "";
16795 switch ( element )
16796 {
16797 case SHEET_ATK:
16798 if ( attackHoverTextInfo.proficiency == -1 )
16799 {
16800 glyphIcon->path = getHoverTextString("icon_atk_path");
16801 }
16802 else if (attackHoverTextInfo.proficiency >= 0 && attackHoverTextInfo.proficiency < NUMPROFICIENCIES )
16803 {
16804 for ( auto& skill : player.skillSheet.skillSheetData.skillEntries )
16805 {
16806 if ( skill.skillId == attackHoverTextInfo.proficiency )
16807 {
16808 if ( skillCapstoneUnlocked(player.playernum, attackHoverTextInfo.proficiency) )
16809 {
16810 glyphIcon->path = skill.skillIconPathLegend;
16811 }
16812 else
16813 {
16814 glyphIcon->path = skill.skillIconPath;
16815 }
16816 break;
16817 }
16818 }
16819 if ( stats[player.playernum]->getModifiedProficiency(attackHoverTextInfo.proficiency) >= SKILL_LEVEL_LEGENDARY )
16820 {
16821 glyphBacking->path = actionPromptBackingIconPath100;
16822 }
16823 else if ( stats[player.playernum]->getModifiedProficiency(attackHoverTextInfo.proficiency) >= SKILL_LEVEL_EXPERT )
16824 {
16825 glyphBacking->path = actionPromptBackingIconPath60;
16826 }
16827 else if ( stats[player.playernum]->getModifiedProficiency(attackHoverTextInfo.proficiency) >= SKILL_LEVEL_BASIC )
16828 {
16829 glyphBacking->path = actionPromptBackingIconPath20;
16830 }
16831 else
16832 {
16833 glyphBacking->path = actionPromptBackingIconPath00;
16834 }
16835 }
16836 break;
16837 case SHEET_AC:
16838 glyphIcon->path = getHoverTextString("icon_ac_path");
16839 for ( auto& skill : player.skillSheet.skillSheetData.skillEntries )
16840 {
16841 if ( skill.skillId == PRO_SHIELD )
16842 {
16843 if ( skillCapstoneUnlocked(player.playernum, PRO_SHIELD) )
16844 {
16845 glyphIcon->path = skill.skillIconPathLegend;
16846 }
16847 else
16848 {
16849 glyphIcon->path = skill.skillIconPath;
16850 }
16851 break;
16852 }
16853 }
16854 if ( stats[player.playernum]->getModifiedProficiency(PRO_SHIELD) >= SKILL_LEVEL_LEGENDARY )
16855 {
16856 glyphBacking->path = actionPromptBackingIconPath100;
16857 }
16858 else if ( stats[player.playernum]->getModifiedProficiency(PRO_SHIELD) >= SKILL_LEVEL_EXPERT )
16859 {
16860 glyphBacking->path = actionPromptBackingIconPath60;
16861 }
16862 else if ( stats[player.playernum]->getModifiedProficiency(PRO_SHIELD) >= SKILL_LEVEL_BASIC )
16863 {
16864 glyphBacking->path = actionPromptBackingIconPath20;
16865 }
16866 else
16867 {
16868 glyphBacking->path = actionPromptBackingIconPath00;
16869 }
16870 break;
16871 case SHEET_POW:
16872 glyphIcon->path = getHoverTextString("icon_pwr_path");
16873 break;
16874 case SHEET_RES:
16875 glyphIcon->path = getHoverTextString("icon_res_path");
16876 break;
16877 case SHEET_RGN:
16878 glyphIcon->path = getHoverTextString("icon_rgn_hp_path");
16879 break;
16880 case SHEET_RGN_MP:
16881 if ( isAutomatonHTRegen )
16882 {
16883 if ( stats[player.playernum]->HUNGER <= 300 )
16884 {
16885 glyphIcon->path = getHoverTextString("icon_rgn_ht_empty_path");
16886 }
16887 else if ( stats[player.playernum]->HUNGER > 1200 )
16888 {
16889 glyphIcon->path = getHoverTextString("icon_rgn_ht_superheated_path");
16890 }
16891 else
16892 {
16893 glyphIcon->path = getHoverTextString("icon_rgn_ht_normal_path");
16894 }
16895 }
16896 else
16897 {
16898 glyphIcon->path = getHoverTextString("icon_rgn_mp_path");
16899 }
16900 break;
16901 case SHEET_WGT:
16902 glyphIcon->path = getHoverTextString("icon_wgt_path");
16903 break;
16904 default:
16905 break;
16906 }
16907 glyphIcon->pos.w = 24;
16908 glyphIcon->pos.h = 24;
16909 glyphIcon->pos.x = glyphBacking->pos.x + glyphBacking->pos.w / 2 - glyphIcon->pos.w / 2;
16910 glyphIcon->pos.y = glyphBacking->pos.y + glyphBacking->pos.h / 2 - glyphIcon->pos.h / 2;
16911
16912 SDL_Rect entryPos = entry->getSize();
16913 entryPos.x = padx / 2 + glyphBacking->pos.x + glyphBacking->pos.w;
16914 entryPos.y = currentHeight;
16915 if ( element == SHEET_ATK )
16916 {
16917 if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_THROWN
16918 || attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_THROWN_GEM
16919 || attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_PICKAXE )
16920 {
16921 // fewer lines, add offset to centre the lines with the glyph
16922 entryPos.y += 8;
16923 }
16924 else if ( attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_DEFAULT
16925 || attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_TOOL
16926 || attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_TOOL_TRAP
16927 || attackHoverTextInfo.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_MAGICSTAFF )
16928 {
16929 entryPos.y += 16;
16930 }
16931 }
16932 else if ( element == SHEET_RGN_MP && isInsectoidENRegen )
16933 {
16934 entryPos.y += 16;
16935 }
16936 entryPos.w = txtPos.w - (padxMid + glyphBacking->pos.x + glyphBacking->pos.w);
16937 entry->setSize(entryPos);
16938 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry1 != entry->getText() )
16939 {
16940 entry->reflowTextToFit(0);
16941 charsheetTooltipCache[player.playernum].textEntries[element].entry1 = entry->getText();
16942 }
16943 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
16944 entry->setSize(entryPos);
16945 entry->setColor(defaultColor);
16946 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
16947 tooltipPos.h = pady1 + currentHeight + pady2;
16948
16949 auto entryValue = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
16950 ++currentTextFieldIndex;
16951 entryValue->setDisabled(false);
16952 int value = 0;
16953 switch ( element )
16954 {
16955 case SHEET_ATK:
16956 break;
16957 case SHEET_AC:
16958 break;
16959 case SHEET_POW:
16960 break;
16961 case SHEET_RES:
16962 break;
16963 case SHEET_RGN:
16964 break;
16965 case SHEET_WGT:
16966 break;
16967 default:
16968 break;
16969 }
16970 entryValue->setColor(hudColors.characterSheetNeutral);
16971 if ( value < 0 )
16972 {
16973 entryValue->setColor(hudColors.characterSheetRed);
16974 }
16975 entryValue->setText(valueBuf);
16976 SDL_Rect entryValuePos = entry->getSize();
16977 entryValue->setSize(entryValuePos);
16978 entryValue->setHJustify(Frame::justify_t::RIGHT);
16979 entryValue->setVJustify(Field::justify_t::TOP);
16980
16981 /*auto txtValueBackingFrame = tooltipFrame->findFrame("txt value backing frame 1");
16982 SDL_Rect backingFramePos = entryValue->getSize();
16983 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
16984 entryValue->getTextColor(), entryValue->getOutlineColor());
16985 longestValue = std::max(longestValue, txtValueGet->getWidth());
16986 backingFramePos.x = backingFramePos.x + backingFramePos.w;
16987 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
16988 valueSizes[1] = std::make_pair(entryValue, backingFramePos);
16989 txtValueBackingFrame->setDisabled(false);*/
16990 ++currentTextBackingFrameIndex;
16991 }
16992
16993 if ( element == SHEET_ATK && getAttackTooltipLines(player.playernum, attackHoverTextInfo, 2, buf, valueBuf)
16994 || element != SHEET_ATK )
16995 {
16996 currentHeight += 0;
16997 auto entry = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
16998 ++currentTextFieldIndex;
16999 entry->setDisabled(false);
17000 switch ( element )
17001 {
17002 case SHEET_ATK:
17003 break;
17004 case SHEET_AC:
17005 {
17006 std::string skillName = "";
17007 int skillLVL = 0;
17008 for ( auto& skill : player.skillSheet.skillSheetData.skillEntries )
17009 {
17010 if ( skill.skillId == PRO_SHIELD )
17011 {
17012 skillName = skill.name;
17013 skillLVL = stats[player.playernum]->getModifiedProficiency(skill.skillId);
17014 break;
17015 }
17016 }
17017 snprintf(buf, sizeof(buf), getHoverTextString("attributes_ac_defending").c_str(), skillName.c_str(), skillLVL);
17018 std::string tag = "BLOCK_AC_INCREASE";
17019 std::string blockBonus = formatSkillSheetEffects(player.playernum, PRO_SHIELD, tag, getHoverTextString("attributes_ac_bonus_format"));
17020 snprintf(valueBuf, sizeof(valueBuf), "%s", blockBonus.c_str());
17021 }
17022 break;
17023 case SHEET_POW:
17024 {
17025 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_pwr_spellbook").c_str());
17026 std::string tag = "MAGIC_SPELLPOWER_INT";
17027 std::string formatValue = "%d";
17028 std::string pwrBonus = formatSkillSheetEffects(player.playernum, PRO_MAGIC, tag, formatValue);
17029 Sint32 pwr = std::stoi(pwrBonus) / 2;
17030 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_pwr_bonus_format").c_str(), pwr);
17031 }
17032 break;
17033 case SHEET_RES:
17034 {
17035 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_res_sources").c_str());
17036 int sources = Entity::getMagicResistance(stats[player.playernum]);
17037 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_res_sources_format").c_str(), sources);
17038 }
17039 break;
17040 case SHEET_RGN:
17041 {
17042 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_sources").c_str());
17043 int sources = Entity::getHealringFromEquipment(players[player.playernum]->entity, *stats[player.playernum], true);
17044 sources += Entity::getHealringFromEffects(players[player.playernum]->entity, *stats[player.playernum]);
17045 sources = std::min(sources, 3);
17046 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_hp_sources_format").c_str(), sources);
17047 }
17048 break;
17049 case SHEET_RGN_MP:
17050 {
17051 if ( isInsectoidENRegen )
17052 {
17053 //snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_en_sources").c_str());
17054 snprintf(buf, sizeof(buf), "");
17055 snprintf(valueBuf, sizeof(valueBuf), "");
17056 }
17057 else
17058 {
17059 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_sources").c_str());
17060 int sources = Entity::getManaringFromEquipment(players[player.playernum]->entity, *stats[player.playernum], true);
17061 sources += Entity::getManaringFromEffects(players[player.playernum]->entity, *stats[player.playernum]);
17062 sources = std::min(sources, 3);
17063 if ( isAutomatonHTRegen )
17064 {
17065 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_ht_sources_format").c_str(), sources);
17066 }
17067 else
17068 {
17069 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_mp_sources_format").c_str(), sources);
17070 }
17071 }
17072 }
17073 break;
17074 case SHEET_WGT:
17075 {
17076 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_wgt_movement_speed").c_str());
17077 int weight = player.movement.getCharacterModifiedWeight();
17078 Sint32 STR = statGetSTR(stats[player.playernum], player.entity);
17079 Sint32 DEX = statGetDEX(stats[player.playernum], player.entity);
17080 real_t currentSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(weight, STR), DEX);
17081 real_t maxSpeed = player.movement.getMaximumSpeed();
17082 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_wgt_speed_format").c_str(),
17083 100.0 * currentSpeed / std::fmax(.01, maxSpeed));
17084 }
17085 break;
17086 default:
17087 break;
17088 }
17089 entry->setText(buf);
17090 entry->setVJustify(Field::justify_t::TOP);
17091
17092 auto glyphBacking = tooltipFrame->findImage("glyph 1");
17093
17094 SDL_Rect entryPos = entry->getSize();
17095 entryPos.x = padx / 2 + glyphBacking->pos.x + glyphBacking->pos.w;
17096 entryPos.y = currentHeight;
17097 entryPos.w = txtPos.w - (padxMid + glyphBacking->pos.x + glyphBacking->pos.w);
17098 entry->setSize(entryPos);
17099 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry2 != entry->getText() )
17100 {
17101 entry->reflowTextToFit(0);
17102 charsheetTooltipCache[player.playernum].textEntries[element].entry2 = entry->getText();
17103 }
17104 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
17105 entry->setSize(entryPos);
17106 entry->setColor(defaultColor);
17107
17108 if ( strcmp(buf, "") )
17109 {
17110 // don't modify height if this is an empty line
17111 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
17112 }
17113 tooltipPos.h = pady1 + currentHeight + pady2;
17114
17115 auto entryValue = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
17116 ++currentTextFieldIndex;
17117 entryValue->setDisabled(false);
17118 int value = 0;
17119 switch ( element )
17120 {
17121 case SHEET_ATK:
17122 break;
17123 case SHEET_AC:
17124 break;
17125 case SHEET_POW:
17126 break;
17127 case SHEET_RES:
17128 break;
17129 case SHEET_RGN:
17130 break;
17131 case SHEET_WGT:
17132 break;
17133 default:
17134 break;
17135 }
17136 entryValue->setColor(hudColors.characterSheetNeutral);
17137 if ( value < 0 )
17138 {
17139 entryValue->setColor(hudColors.characterSheetRed);
17140 }
17141 else if ( value > 0 )
17142 {
17143 entryValue->setColor(hudColors.characterSheetGreen);
17144 }
17145 entryValue->setText(valueBuf);
17146 SDL_Rect entryValuePos = entry->getSize();
17147 entryValue->setSize(entryValuePos);
17148 entryValue->setHJustify(Frame::justify_t::RIGHT);
17149 entryValue->setVJustify(Field::justify_t::TOP);
17150
17151 /*auto txtValueBackingFrame = tooltipFrame->findFrame("txt value backing frame 2");
17152 SDL_Rect backingFramePos = entryValue->getSize();
17153 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
17154 entryValue->getTextColor(), entryValue->getOutlineColor());
17155 longestValue = std::max(longestValue, txtValueGet->getWidth());
17156 backingFramePos.x = backingFramePos.x + backingFramePos.w;
17157 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
17158 valueSizes[2] = std::make_pair(entryValue, backingFramePos);
17159 txtValueBackingFrame->setDisabled(false);*/
17160 ++currentTextBackingFrameIndex;
17161 }
17162 bool hasEntryInfoLines = false;
17163 if ( element == SHEET_ATK && getAttackTooltipLines(player.playernum, attackHoverTextInfo, 3, buf, valueBuf)
17164 || (element != SHEET_ATK
17165 && !(element == SHEET_RGN && !(svFlags & SV_FLAG_HUNGER))
17166 && !(element == SHEET_RGN_MP && isInsectoidENRegen && !(svFlags & SV_FLAG_HUNGER))
17167 ))
17168 {
17169 // extra number display - line 3
17170 hasEntryInfoLines = true;
17171 if ( element == SHEET_ATK || element == SHEET_AC || element == SHEET_POW || element == SHEET_RES || element == SHEET_RGN
17172 || element == SHEET_RGN_MP || element == SHEET_WGT )
17173 {
17174 if ( element == SHEET_RGN_MP && isInsectoidENRegen )
17175 {
17176 currentHeight += 8;
17177 }
17178
17179 // add a divider
17180 div2->pos.x = padx;
17181 div2->pos.y = currentHeight + 2 + actualFont->height(true);
17182 div2->pos.w = txtPos.w;
17183 div2->disabled = false;
17184
17185 auto entryTotalHeading = characterSheetTooltipTextFields[player.playernum][16]; assert(entryTotalHeading)(static_cast<void> (0));
17186 entryTotalHeading->setDisabled(false);
17187 if ( element == SHEET_ATK )
17188 {
17189 entryTotalHeading->setText(getHoverTextString("attributes_atk_total_sum_header").c_str());
17190 }
17191 else if ( element == SHEET_AC )
17192 {
17193 entryTotalHeading->setText(getHoverTextString("attributes_ac_base_sum_header").c_str());
17194 }
17195 else if ( element == SHEET_POW )
17196 {
17197 entryTotalHeading->setText(getHoverTextString("attributes_pwr_base_sum_header").c_str());
17198 }
17199 else if ( element == SHEET_RES )
17200 {
17201 entryTotalHeading->setText(getHoverTextString("attributes_res_base_sum_header").c_str());
17202 }
17203 else if ( element == SHEET_RGN )
17204 {
17205 entryTotalHeading->setText(getHoverTextString("attributes_rgn_hp_base_sum_header").c_str());
17206 }
17207 else if ( element == SHEET_RGN_MP )
17208 {
17209 if ( isAutomatonHTRegen )
17210 {
17211 entryTotalHeading->setText(getHoverTextString("attributes_rgn_ht_base_sum_header").c_str());
17212 }
17213 else if ( isInsectoidENRegen )
17214 {
17215 entryTotalHeading->setText(getHoverTextString("attributes_rgn_en_base_sum_header").c_str());
17216 }
17217 else
17218 {
17219 entryTotalHeading->setText(getHoverTextString("attributes_rgn_mp_base_sum_header").c_str());
17220 }
17221 }
17222 else if ( element == SHEET_WGT )
17223 {
17224 entryTotalHeading->setText(getHoverTextString("attributes_wgt_base_sum_header").c_str());
17225 }
17226 entryTotalHeading->setColor(hudColors.characterSheetOffWhiteText);
17227 entryTotalHeading->setHJustify(Field::justify_t::RIGHT);
17228 SDL_Rect entryPos = entryTotalHeading->getSize();
17229 entryPos.x = padx + padxMid;
17230 entryPos.y = currentHeight + 1 - (extraTextHeightForLowerCharacters / 2);
17231 entryPos.w = txtPos.w - (padxMid * 2);
17232 entryPos.h = actualFont->height(true) + extraTextHeightForLowerCharacters;
17233 entryTotalHeading->setSize(entryPos);
17234
17235 currentHeight += actualFont->height(true) + 2; // extra gap here for 'total' text.
17236 }
17237 currentHeight += padyMid;
17238 auto entry = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
17239 ++currentTextFieldIndex;
17240 entry->setDisabled(false);
17241 int value = 0;
17242 switch ( element )
17243 {
17244 case SHEET_ATK:
17245 break;
17246 case SHEET_AC:
17247 {
17248 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_ac_entry_attr_bonus").c_str());
17249 Sint32 CON = statGetCON(stats[player.playernum], players[player.playernum]->entity);
17250 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_ac_bonus_format").c_str(), CON);
17251 }
17252 break;
17253 case SHEET_POW:
17254 {
17255 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_pwr_base_value").c_str());
17256 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_pwr_bonus_format").c_str(), 100);
17257 }
17258 break;
17259 case SHEET_RES:
17260 {
17261 Monster type = stats[player.playernum]->type;
17262 std::string appearance = "";
17263 bool aestheticOnly = false;
17264 if ( player.entity )
17265 {
17266 if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN )
17267 {
17268 if ( stats[player.playernum]->appearance != 0 )
17269 {
17270 aestheticOnly = true;
17271 appearance = Language::get(4068);
17272 type = player.entity->getMonsterFromPlayerRace(stats[player.playernum]->playerRace);
17273 }
17274 }
17275 }
17276 std::string race = getMonsterLocalizedName(type).c_str();
17277 capitalizeString(race);
17278
17279 snprintf(buf, sizeof(buf), getHoverTextString("attributes_res_base_value").c_str(), race.c_str());
17280 Sint32 baseResist = damagetables[stats[player.playernum]->type][DAMAGE_TABLE_MAGIC] * 100;
17281 baseResist = 100 - baseResist;
17282 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_res_bonus_format").c_str(), baseResist);
17283 }
17284 break;
17285 case SHEET_RGN:
17286 {
17287 Monster type = stats[player.playernum]->type;
17288 bool aestheticOnly = false;
17289 if ( player.entity )
17290 {
17291 if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN )
17292 {
17293 if ( stats[player.playernum]->appearance != 0 )
17294 {
17295 aestheticOnly = true;
17296 type = player.entity->getMonsterFromPlayerRace(stats[player.playernum]->playerRace);
17297 }
17298 }
17299 }
17300 std::string race = getMonsterLocalizedName(type).c_str();
17301 capitalizeString(race);
17302
17303 snprintf(buf, sizeof(buf), getHoverTextString("attributes_rgn_base_value").c_str(), race.c_str());
17304 real_t regen = 100.0;
17305 if ( type == SKELETON )
17306 {
17307 regen = 25.0;
17308 }
17309 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_bonus_format").c_str(), regen);
17310 }
17311 break;
17312 case SHEET_RGN_MP:
17313 {
17314 if ( isAutomatonHTRegen )
17315 {
17316 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_ht_base_bonus").c_str());
17317 real_t baseHTModifier = 100.f;
17318 if ( stats[player.playernum]->HUNGER <= 300 )
17319 {
17320 int baseTime = getBaseManaRegen(player.entity, *stats[player.playernum]);
17321 real_t scaledInterval = ((60 * baseTime) / (std::max(stats[player.playernum]->MAXMP, 1)));
17322 baseHTModifier = scaledInterval / TICKS_PER_SECOND50;
17323 baseHTModifier /= -6.0; // degrade faster
17324 real_t nominalRegen = MAGIC_REGEN_TIME300 / TICKS_PER_SECOND50;
17325 baseHTModifier = (nominalRegen / baseHTModifier) * 100.0;
17326 }
17327 else if ( stats[player.playernum]->HUNGER > 1200 )
17328 {
17329 if ( stats[player.playernum]->MP / static_cast<real_t>(std::max(1, stats[player.playernum]->MAXMP)) <= 0.5 )
17330 {
17331 baseHTModifier *= 4; // increase faster at < 50% mana
17332 }
17333 else
17334 {
17335 baseHTModifier *= 2; // increase less faster at > 50% mana
17336 }
17337 }
17338 else
17339 {
17340 // normal manaRegenInterval 300-1200 hunger.
17341 }
17342 //baseHTModifier /= 100.0;
17343 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_ht_bonus_format").c_str(), baseHTModifier);
17344 }
17345 else if ( isInsectoidENRegen )
17346 {
17347 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_en_base_bonus").c_str());
17348
17349 real_t normalRegenTime = (1000.f * 30 * 1.5) / static_cast<float>(TICKS_PER_SECOND50); // 30 base, insectoid does 1.5x in getHungerTickRate()
17350 //normalRegenTime = (1000.f * (Entity::getHungerTickRate(stats[player.playernum], true, true)) / static_cast<float>(TICKS_PER_SECOND));
17351 normalRegenTime /= (std::max(stats[player.playernum]->MAXMP, 1)); // time for 1 mana in seconds
17352 normalRegenTime *= TICKS_PER_SECOND50; // game ticks for 1 mana
17353
17354 real_t modifiedRegenTime = (1000.f * (Entity::getHungerTickRate(stats[player.playernum], true, false)) / static_cast<float>(TICKS_PER_SECOND50));
17355 modifiedRegenTime /= (std::max(stats[player.playernum]->MAXMP, 1)); // time for 1 mana in seconds
17356 modifiedRegenTime *= TICKS_PER_SECOND50; // game ticks for 1 mana
17357
17358 real_t displayValue = 100.0 * (normalRegenTime / modifiedRegenTime);
17359
17360 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_en_bonus_format").c_str(), displayValue);
17361 }
17362 else
17363 {
17364 Monster type = stats[player.playernum]->type;
17365 bool aestheticOnly = false;
17366 if ( player.entity )
17367 {
17368 if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN )
17369 {
17370 if ( stats[player.playernum]->appearance != 0 )
17371 {
17372 aestheticOnly = true;
17373 type = player.entity->getMonsterFromPlayerRace(stats[player.playernum]->playerRace);
17374 }
17375 }
17376 }
17377 std::string race = getMonsterLocalizedName(type).c_str();
17378 capitalizeString(race);
17379
17380 snprintf(buf, sizeof(buf), getHoverTextString("attributes_rgn_base_value").c_str(), race.c_str());
17381 real_t regen = 100.0;
17382 if ( type == SKELETON )
17383 {
17384 regen = 25.0;
17385 }
17386 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_bonus_format").c_str(), regen);
17387 }
17388 }
17389 break;
17390 case SHEET_WGT:
17391 {
17392 int weight = player.movement.getCharacterModifiedWeight();
17393 //int equippedWeightTotal = player.movement.getCharacterEquippedWeight();
17394 //int equippedWeight = player.movement.getCharacterModifiedWeight(&equippedWeightTotal);
17395 //int goldWeightTotal = stats[player.playernum]->GOLD / 100;
17396 //int goldWeight = player.movement.getCharacterModifiedWeight(&goldWeightTotal);
17397 Sint32 STR = statGetSTR(stats[player.playernum], player.entity);
17398 Sint32 DEX = statGetDEX(stats[player.playernum], player.entity);
17399 //real_t currentEquippedSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(equippedWeight, STR), DEX);
17400 real_t currentSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(weight, STR), DEX);
17401 real_t noWeightSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(0, STR), DEX);
17402 //real_t goldSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(goldWeight, STR), DEX);
17403 real_t maxSpeed = player.movement.getMaximumSpeed();
17404
17405 real_t currentSpeedPercent = 100.0 * currentSpeed / std::fmax(.01, maxSpeed);
17406 //real_t currentEquippedSpeedPercent = 100.0 * currentEquippedSpeed / std::fmax(.01, maxSpeed);
17407 real_t noWeightSpeedPercent = 100.0 * noWeightSpeed / std::fmax(.01, maxSpeed);
17408 //real_t goldSpeedPercent = 100.0 * goldSpeed / std::fmax(.01, maxSpeed);
17409
17410 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_wgt_attributes_bonus").c_str());
17411 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_wgt_speed_bonus_format").c_str(),
17412 currentSpeedPercent + (noWeightSpeedPercent - currentSpeedPercent) /*+ (goldSpeedPercent - noWeightSpeedPercent)*/);
17413 }
17414 break;
17415 default:
17416 break;
17417 }
17418 entry->setText(buf);
17419 entry->setVJustify(Field::justify_t::TOP);
17420
17421 SDL_Rect entryPos = entry->getSize();
17422 entryPos.x = padx + padxMid;
17423 entryPos.y = currentHeight;
17424 entryPos.w = txtPos.w - (padxMid * 2);
17425 entry->setSize(entryPos);
17426 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry3 != entry->getText() )
17427 {
17428 entry->reflowTextToFit(0);
17429 charsheetTooltipCache[player.playernum].textEntries[element].entry3 = entry->getText();
17430 }
17431 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
17432 entry->setSize(entryPos);
17433 entry->setColor(defaultColor);
17434 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
17435 tooltipPos.h = pady1 + currentHeight + pady2;
17436
17437 auto entryValue = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
17438 ++currentTextFieldIndex;
17439 entryValue->setDisabled(false);
17440 switch ( element )
17441 {
17442 case SHEET_ATK:
17443 break;
17444 case SHEET_AC:
17445 break;
17446 case SHEET_POW:
17447 break;
17448 case SHEET_RES:
17449 break;
17450 case SHEET_RGN:
17451 break;
17452 case SHEET_WGT:
17453 break;
17454 default:
17455 break;
17456 }
17457 entryValue->setColor(hudColors.characterSheetNeutral);
17458 if ( value < 0 )
17459 {
17460 entryValue->setColor(hudColors.characterSheetRed);
17461 }
17462 else if ( value > 0 )
17463 {
17464 entryValue->setColor(hudColors.characterSheetGreen);
17465 }
17466 entryValue->setText(valueBuf);
17467 entryValue->setSize(entry->getSize());
17468 entryValue->setHJustify(Frame::justify_t::LEFT);
17469 entryValue->setVJustify(Field::justify_t::TOP);
17470
17471 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][currentTextBackingFrameIndex];
17472 SDL_Rect backingFramePos = entryValue->getSize();
17473 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
17474 entryValue->getTextColor(), entryValue->getOutlineColor());
17475 longestValue = std::max(longestValue, txtValueGet->getWidth());
17476 backingFramePos.x = backingFramePos.x + backingFramePos.w;
17477 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
17478 valueSizes[currentTextBackingFrameIndex] = std::make_pair(entryValue, backingFramePos);
17479 txtValueBackingFrame->setDisabled(false);
17480 ++currentTextBackingFrameIndex;
17481 }
17482
17483 if ( (element == SHEET_ATK && getAttackTooltipLines(player.playernum, attackHoverTextInfo, 4, buf, valueBuf))
17484 || (element != SHEET_ATK
17485 && !(element == SHEET_RGN && !(svFlags & SV_FLAG_HUNGER))
17486 && !(element == SHEET_RGN_MP && isInsectoidENRegen && !(svFlags & SV_FLAG_HUNGER))
17487 )
17488 )
17489 {
17490 // extra number display - line 4
17491 hasEntryInfoLines = true;
17492 currentHeight += padyMid;
17493 auto entry = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
17494 ++currentTextFieldIndex;
17495 entry->setDisabled(false);
17496 int value = 0;
17497 switch ( element )
17498 {
17499 case SHEET_ATK:
17500 break;
17501 case SHEET_AC:
17502 {
17503 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_ac_entry_items_bonus").c_str());
17504 Sint32 CON = statGetCON(stats[player.playernum], players[player.playernum]->entity);
17505
17506 Sint32 oldSkillLVL = stats[player.playernum]->getProficiency(PRO_SHIELD);
17507 bool oldDefending = stats[player.playernum]->defending;
17508 stats[player.playernum]->defending = false;
17509 stats[player.playernum]->setProficiencyUnsafe(PRO_SHIELD, -999);
17510
17511 Sint32 armor = AC(stats[player.playernum]);
17512 stats[player.playernum]->defending = oldDefending;
17513 stats[player.playernum]->setProficiency(PRO_SHIELD, oldSkillLVL);
17514
17515 Sint32 itemsEffectBonus = armor - CON;
17516 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_ac_bonus_format").c_str(), itemsEffectBonus);
17517 }
17518 break;
17519 case SHEET_POW:
17520 {
17521 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_pwr_entry_attr_bonus").c_str());
17522 std::string tag = "MAGIC_SPELLPOWER_INT";
17523 std::string pwrINTBonus = formatSkillSheetEffects(player.playernum, PRO_MAGIC, tag, getHoverTextString("attributes_pwr_bonus_format"));
17524 snprintf(valueBuf, sizeof(valueBuf), "%s", pwrINTBonus.c_str());
17525 }
17526 break;
17527 case SHEET_RES:
17528 {
17529 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_res_entry_items_bonus").c_str());
17530 Sint32 baseResist = 100 * damagetables[stats[player.playernum]->type][DAMAGE_TABLE_MAGIC];
17531 baseResist = 100 - baseResist;
17532 real_t resistance = 100.0 * Entity::getDamageTableMultiplier(player.entity, *stats[player.playernum], DAMAGE_TABLE_MAGIC);
17533 resistance /= (Entity::getMagicResistance(stats[player.playernum]) + 1);
17534 resistance = (100.0 - resistance);
17535 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_res_bonus_format").c_str(), (int)resistance - baseResist);
17536 }
17537 break;
17538 case SHEET_RGN:
17539 {
17540 Monster type = stats[player.playernum]->type;
17541 bool aestheticOnly = false;
17542 if ( player.entity )
17543 {
17544 if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN )
17545 {
17546 if ( stats[player.playernum]->appearance != 0 )
17547 {
17548 aestheticOnly = true;
17549 type = player.entity->getMonsterFromPlayerRace(stats[player.playernum]->playerRace);
17550 }
17551 }
17552 }
17553 real_t baseRegen = 100.0;
17554 if ( type == SKELETON )
17555 {
17556 baseRegen = 25.0;
17557 }
17558
17559 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_entry_items_bonus").c_str());
17560 real_t regen = getDisplayedHPRegen(player.entity, *stats[player.playernum], nullptr, nullptr);
17561 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_bonus_format").c_str(), regen - baseRegen);
17562 }
17563 break;
17564 case SHEET_RGN_MP:
17565 {
17566 if ( isAutomatonHTRegen )
17567 {
17568 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_entry_items_bonus").c_str());
17569 real_t baseHTModifier = 100.f;
17570 if ( stats[player.playernum]->HUNGER <= 300 )
17571 {
17572 int baseTime = getBaseManaRegen(player.entity, *stats[player.playernum]);
17573 real_t scaledInterval = ((60 * baseTime) / (std::max(stats[player.playernum]->MAXMP, 1)));
17574 baseHTModifier = scaledInterval / TICKS_PER_SECOND50;
17575 baseHTModifier /= -6.0; // degrade faster
17576 real_t nominalRegen = MAGIC_REGEN_TIME300 / TICKS_PER_SECOND50;
17577 baseHTModifier = (nominalRegen / baseHTModifier) * 100.0;
17578 }
17579 else if ( stats[player.playernum]->HUNGER > 1200 )
17580 {
17581 if ( stats[player.playernum]->MP / static_cast<real_t>(std::max(1, stats[player.playernum]->MAXMP)) <= 0.5 )
17582 {
17583 baseHTModifier *= 4; // increase faster at < 50% mana
17584 }
17585 else
17586 {
17587 baseHTModifier *= 2; // increase less faster at > 50% mana
17588 }
17589 }
17590 else
17591 {
17592 // normal manaRegenInterval 300-1200 hunger.
17593 }
17594 real_t regenTotal = getDisplayedMPRegen(player.entity, *stats[player.playernum], nullptr, nullptr);
17595 real_t displayTotal = (regenTotal - baseHTModifier);
17596 //displayTotal /= 100.0;
17597 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_ht_bonus_format").c_str(), displayTotal);
17598 }
17599 else if ( isInsectoidENRegen )
17600 {
17601 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_en_entry_items_bonus").c_str());
17602
17603 real_t normalRegenTime = (1000.f * 30 * 1.5) / static_cast<float>(TICKS_PER_SECOND50); // 30 base, insectoid does 1.5x in getHungerTickRate()
17604 normalRegenTime /= (std::max(stats[player.playernum]->MAXMP, 1)); // time for 1 mana in seconds
17605 normalRegenTime *= TICKS_PER_SECOND50; // game ticks for 1 mana
17606
17607 real_t modifiedRegenTime = (1000.f * (Entity::getHungerTickRate(stats[player.playernum], true, false)) / static_cast<float>(TICKS_PER_SECOND50));
17608 modifiedRegenTime /= (std::max(stats[player.playernum]->MAXMP, 1)); // time for 1 mana in seconds
17609 modifiedRegenTime *= TICKS_PER_SECOND50; // game ticks for 1 mana
17610
17611 real_t baseValue = 100.0 * (normalRegenTime / modifiedRegenTime);
17612 real_t displayedValue = getDisplayedMPRegen(player.entity, *stats[player.playernum], nullptr, nullptr);
17613
17614 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_en_bonus_format").c_str(), displayedValue - baseValue);
17615 }
17616 else
17617 {
17618 Monster type = stats[player.playernum]->type;
17619 bool aestheticOnly = false;
17620 if ( player.entity )
17621 {
17622 if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN )
17623 {
17624 if ( stats[player.playernum]->appearance != 0 )
17625 {
17626 aestheticOnly = true;
17627 type = player.entity->getMonsterFromPlayerRace(stats[player.playernum]->playerRace);
17628 }
17629 }
17630 }
17631 real_t baseRegen = 100.0;
17632 if ( type == SKELETON )
17633 {
17634 baseRegen = 25.0;
17635 }
17636
17637 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_entry_items_bonus").c_str());
17638 Sint32 oldINT = stats[player.playernum]->INT;
17639 stats[player.playernum]->INT += -statGetINT(stats[player.playernum], player.entity);
17640 real_t regenWithoutINT = getDisplayedMPRegen(player.entity, *stats[player.playernum], nullptr, nullptr);
17641 stats[player.playernum]->INT = oldINT;
17642 real_t regenTotal = getDisplayedMPRegen(player.entity, *stats[player.playernum], nullptr, nullptr);
17643 real_t regenStatSkill = regenTotal - regenWithoutINT;
17644
17645 real_t regenItemsEffects = regenTotal - regenStatSkill - baseRegen;
17646 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_bonus_format").c_str(), regenItemsEffects);
17647 }
17648 }
17649 break;
17650 case SHEET_WGT:
17651 {
17652 int weight = player.movement.getCharacterModifiedWeight();
17653 int equippedWeightTotal = player.movement.getCharacterEquippedWeight();
17654 int equippedWeight = player.movement.getCharacterModifiedWeight(&equippedWeightTotal);
17655 Sint32 STR = statGetSTR(stats[player.playernum], player.entity);
17656 Sint32 DEX = statGetDEX(stats[player.playernum], player.entity);
17657 real_t currentEquippedSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(equippedWeight, STR), DEX);
17658 //real_t currentSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(weight, STR), DEX);
17659 real_t noWeightSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(0, STR), DEX);
17660 real_t maxSpeed = player.movement.getMaximumSpeed();
17661
17662 //real_t currentSpeedPercent = 100.0 * currentSpeed / std::fmax(.01, maxSpeed);
17663 real_t currentEquippedSpeedPercent = 100.0 * currentEquippedSpeed / std::fmax(.01, maxSpeed);
17664 real_t noWeightSpeedPercent = 100.0 * noWeightSpeed / std::fmax(.01, maxSpeed);
17665
17666 real_t displayValue = (currentEquippedSpeedPercent - noWeightSpeedPercent);
17667 if ( displayValue >= 0.0 )
17668 {
17669 displayValue = -.000001; // so there is a negative sign
17670 }
17671
17672 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_wgt_equipment_value").c_str());
17673 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_wgt_speed_bonus_format").c_str(),
17674 displayValue);
17675 }
17676 break;
17677 default:
17678 break;
17679 }
17680 entry->setText(buf);
17681 entry->setVJustify(Field::justify_t::TOP);
17682
17683 SDL_Rect entryPos = entry->getSize();
17684 entryPos.x = padx + padxMid;
17685 entryPos.y = currentHeight;
17686 entryPos.w = txtPos.w - (padxMid * 2);
17687 entry->setSize(entryPos);
17688 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry4 != entry->getText() )
17689 {
17690 entry->reflowTextToFit(0);
17691 charsheetTooltipCache[player.playernum].textEntries[element].entry4 = entry->getText();
17692 }
17693 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
17694 entry->setSize(entryPos);
17695 entry->setColor(defaultColor);
17696 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
17697 tooltipPos.h = pady1 + currentHeight + pady2;
17698
17699 auto entryValue = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
17700 ++currentTextFieldIndex;
17701 entryValue->setDisabled(false);
17702 switch ( element )
17703 {
17704 case SHEET_ATK:
17705 break;
17706 case SHEET_AC:
17707 break;
17708 case SHEET_POW:
17709 break;
17710 case SHEET_RES:
17711 break;
17712 case SHEET_RGN:
17713 break;
17714 case SHEET_WGT:
17715 break;
17716 default:
17717 break;
17718 }
17719 entryValue->setColor(hudColors.characterSheetNeutral);
17720 if ( value < 0 )
17721 {
17722 entryValue->setColor(hudColors.characterSheetRed);
17723 }
17724 else if ( value > 0 )
17725 {
17726 entryValue->setColor(hudColors.characterSheetGreen);
17727 }
17728 entryValue->setText(valueBuf);
17729 entryValue->setSize(entry->getSize());
17730 entryValue->setHJustify(Frame::justify_t::LEFT);
17731 entryValue->setVJustify(Field::justify_t::TOP);
17732
17733 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][currentTextBackingFrameIndex];
17734 SDL_Rect backingFramePos = entryValue->getSize();
17735 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
17736 entryValue->getTextColor(), entryValue->getOutlineColor());
17737 longestValue = std::max(longestValue, txtValueGet->getWidth());
17738 backingFramePos.x = backingFramePos.x + backingFramePos.w;
17739 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
17740 valueSizes[4] = std::make_pair(entryValue, backingFramePos);
17741 txtValueBackingFrame->setDisabled(false);
17742 ++currentTextBackingFrameIndex;
17743 }
17744
17745 if ( element == SHEET_ATK && getAttackTooltipLines(player.playernum, attackHoverTextInfo, 5, buf, valueBuf)
17746 || (element != SHEET_ATK
17747 && element != SHEET_RES
17748 && !(element == SHEET_RGN && !(svFlags & SV_FLAG_HUNGER))
17749 && !(element == SHEET_RGN_MP && isInsectoidENRegen && !(svFlags & SV_FLAG_HUNGER))
17750 )
17751 )
17752 {
17753 // extra number display - line 5
17754 hasEntryInfoLines = true;
17755 currentHeight += padyMid;
17756 auto entry = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
17757 ++currentTextFieldIndex;
17758 entry->setDisabled(false);
17759 int value = 0;
17760 switch ( element )
17761 {
17762 case SHEET_ATK:
17763 break;
17764 case SHEET_AC:
17765 {
17766 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_ac_passive_bonus").c_str());
17767
17768 Sint32 oldSkillLVL = stats[player.playernum]->getProficiency(PRO_SHIELD);
17769 bool oldDefending = stats[player.playernum]->defending;
17770 stats[player.playernum]->defending = false;
17771
17772 Sint32 armor = AC(stats[player.playernum]);
17773 stats[player.playernum]->setProficiencyUnsafe(PRO_SHIELD, -999);
17774 Sint32 armorNoSkill = AC(stats[player.playernum]);
17775 stats[player.playernum]->setProficiency(PRO_SHIELD, oldSkillLVL);
17776 stats[player.playernum]->defending = oldDefending;
17777
17778 Sint32 passiveBonus = armor - armorNoSkill;
17779 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_ac_bonus_format").c_str(), passiveBonus);
17780 }
17781 break;
17782 case SHEET_POW:
17783 {
17784 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_pwr_entry_items_bonus").c_str());
17785 std::string tag = "MAGIC_SPELLPOWER_EQUIPMENT";
17786 std::string pwrINTBonus = formatSkillSheetEffects(player.playernum, PRO_MAGIC, tag, getHoverTextString("attributes_pwr_bonus_format"));
17787 snprintf(valueBuf, sizeof(valueBuf), "%s", pwrINTBonus.c_str());
17788 }
17789 break;
17790 case SHEET_RES:
17791 break;
17792 case SHEET_RGN:
17793 {
17794 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_plain_display").c_str());
17795 real_t regen = (static_cast<real_t>(Entity::getHealthRegenInterval(player.entity, *stats[player.playernum], true)) / TICKS_PER_SECOND50);
17796 if ( regen <= 0.0 )
17797 {
17798 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_hp_per_second_format_zero").c_str(),
17799 (static_cast<real_t>(HEAL_TIME600) / TICKS_PER_SECOND50));
17800 }
17801 else
17802 {
17803 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_hp_per_second_format").c_str(),
17804 regen);
17805 }
17806 }
17807 break;
17808 case SHEET_RGN_MP:
17809 {
17810 if ( isAutomatonHTRegen )
17811 {
17812 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_ht_boiler_status").c_str());
17813 if ( stats[player.playernum]->HUNGER <= 300 )
17814 {
17815 value = -1;
17816 snprintf(valueBuf, sizeof(valueBuf), "%s", getHoverTextString("attributes_rgn_ht_boiler_value_low").c_str());
17817 }
17818 else if ( stats[player.playernum]->HUNGER > 1200 )
17819 {
17820 value = 2;
17821 snprintf(valueBuf, sizeof(valueBuf), "%s", getHoverTextString("attributes_rgn_ht_boiler_value_superheat").c_str());
17822 }
17823 else
17824 {
17825 value = 1;
17826 snprintf(valueBuf, sizeof(valueBuf), "%s", getHoverTextString("attributes_rgn_ht_boiler_value_normal").c_str());
17827 }
17828 }
17829 else if ( isInsectoidENRegen )
17830 {
17831 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_plain_display").c_str());
17832 real_t regen = (static_cast<real_t>(Entity::getManaRegenInterval(player.entity, *stats[player.playernum], true)) / TICKS_PER_SECOND50);
17833 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_en_per_second_format").c_str(), regen);
17834 }
17835 else
17836 {
17837 Monster type = stats[player.playernum]->type;
17838 bool aestheticOnly = false;
17839 if ( player.entity )
17840 {
17841 if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN )
17842 {
17843 if ( stats[player.playernum]->appearance != 0 )
17844 {
17845 aestheticOnly = true;
17846 type = player.entity->getMonsterFromPlayerRace(stats[player.playernum]->playerRace);
17847 }
17848 }
17849 }
17850 real_t baseRegen = 100.0;
17851 if ( type == SKELETON )
17852 {
17853 baseRegen = 25.0;
17854 }
17855
17856 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_entry_statskill_bonus").c_str());
17857 Sint32 oldINT = stats[player.playernum]->INT;
17858 stats[player.playernum]->INT += -statGetINT(stats[player.playernum], player.entity);
17859 real_t regenWithoutINT = getDisplayedMPRegen(player.entity, *stats[player.playernum], nullptr, nullptr);
17860 stats[player.playernum]->INT = oldINT;
17861
17862 real_t regenTotal = getDisplayedMPRegen(player.entity, *stats[player.playernum], nullptr, nullptr);
17863 real_t regenStatSkill = regenTotal - regenWithoutINT;
17864
17865 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_bonus_format").c_str(), regenStatSkill);
17866 }
17867 }
17868 break;
17869 case SHEET_WGT:
17870 {
17871 int weight = player.movement.getCharacterModifiedWeight();
17872 int equippedWeightTotal = player.movement.getCharacterEquippedWeight();
17873 int equippedWeight = player.movement.getCharacterModifiedWeight(&equippedWeightTotal);
17874 int goldWeightTotal = stats[player.playernum]->getGoldWeight();
17875 int goldWeight = player.movement.getCharacterModifiedWeight(&goldWeightTotal);
17876 Sint32 STR = statGetSTR(stats[player.playernum], player.entity);
17877 Sint32 DEX = statGetDEX(stats[player.playernum], player.entity);
17878 real_t currentEquippedSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(equippedWeight, STR), DEX);
17879 real_t currentSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(weight - goldWeight, STR), DEX); // ignore gold
17880 real_t noWeightSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(0, STR), DEX);
17881 real_t maxSpeed = player.movement.getMaximumSpeed();
17882
17883 real_t currentSpeedPercent = 100.0 * currentSpeed / std::fmax(.01, maxSpeed);
17884 real_t currentEquippedSpeedPercent = 100.0 * currentEquippedSpeed / std::fmax(.01, maxSpeed);
17885 real_t noWeightSpeedPercent = 100.0 * noWeightSpeed / std::fmax(.01, maxSpeed);
17886
17887 real_t displayValue = (currentSpeedPercent - noWeightSpeedPercent)
17888 - (currentEquippedSpeedPercent - noWeightSpeedPercent);
17889 if ( displayValue >= 0.0 )
17890 {
17891 displayValue = -.000001; // so there is a negative sign
17892 }
17893
17894 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_wgt_inventory_value").c_str());
17895 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_wgt_speed_bonus_format").c_str(),
17896 displayValue);
17897 }
17898 break;
17899 default:
17900 break;
17901 }
17902 entry->setText(buf);
17903 entry->setVJustify(Field::justify_t::TOP);
17904
17905 SDL_Rect entryPos = entry->getSize();
17906 entryPos.x = padx + padxMid;
17907 entryPos.y = currentHeight;
17908 entryPos.w = txtPos.w - (padxMid * 2);
17909 entry->setSize(entryPos);
17910 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry5 != entry->getText() )
17911 {
17912 entry->reflowTextToFit(0);
17913 charsheetTooltipCache[player.playernum].textEntries[element].entry5 = entry->getText();
17914 }
17915 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
17916 entry->setSize(entryPos);
17917 entry->setColor(defaultColor);
17918 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
17919 tooltipPos.h = pady1 + currentHeight + pady2;
17920
17921 auto entryValue = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
17922 ++currentTextFieldIndex;
17923 entryValue->setDisabled(false);
17924 switch ( element )
17925 {
17926 case SHEET_ATK:
17927 break;
17928 case SHEET_AC:
17929 break;
17930 case SHEET_POW:
17931 break;
17932 case SHEET_RES:
17933 break;
17934 case SHEET_RGN:
17935 break;
17936 case SHEET_WGT:
17937 break;
17938 default:
17939 break;
17940 }
17941 entryValue->setColor(hudColors.characterSheetNeutral);
17942 if ( value < 0 )
17943 {
17944 entryValue->setColor(hudColors.characterSheetRed);
17945 }
17946 else if ( value > 0 )
17947 {
17948 if ( isAutomatonHTRegen )
17949 {
17950 if ( value == 2 ) // supercharge
17951 {
17952 entryValue->setColor(hudColors.characterSheetHeadingText);
17953 }
17954 else
17955 {
17956 entryValue->setColor(hudColors.characterSheetGreen);
17957 }
17958 }
17959 else
17960 {
17961 entryValue->setColor(hudColors.characterSheetGreen);
17962 }
17963 }
17964 entryValue->setText(valueBuf);
17965 entryValue->setSize(entry->getSize());
17966 entryValue->setHJustify(Frame::justify_t::LEFT);
17967 entryValue->setVJustify(Field::justify_t::TOP);
17968
17969 if ( element == SHEET_RGN || (element == SHEET_RGN_MP && (isAutomatonHTRegen || isInsectoidENRegen)) )
17970 {
17971 // special rule here to ignore size of this long line
17972 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][currentTextBackingFrameIndex];
17973 SDL_Rect backingFramePos = entryValue->getSize();
17974 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
17975 entryValue->getTextColor(), entryValue->getOutlineColor());
17976 //longestValue = std::max(longestValue, txtValueGet->getWidth());
17977 backingFramePos.x = backingFramePos.x + backingFramePos.w;
17978 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
17979 //valueSizes[currentTextBackingFrameIndex] = std::make_pair(entryValue, backingFramePos);
17980 txtValueBackingFrame->setDisabled(true);
17981
17982 backingFramePos.w = (int)txtValueGet->getWidth();
17983 backingFramePos.x -= backingFramePos.w;
17984 backingFramePos.x -= 8;
17985 entryValue->setSize(backingFramePos);
17986 ++currentTextBackingFrameIndex;
17987 }
17988 else
17989 {
17990 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][currentTextBackingFrameIndex];
17991 SDL_Rect backingFramePos = entryValue->getSize();
17992 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
17993 entryValue->getTextColor(), entryValue->getOutlineColor());
17994 longestValue = std::max(longestValue, txtValueGet->getWidth());
17995 backingFramePos.x = backingFramePos.x + backingFramePos.w;
17996 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
17997 valueSizes[currentTextBackingFrameIndex] = std::make_pair(entryValue, backingFramePos);
17998 txtValueBackingFrame->setDisabled(false);
17999 ++currentTextBackingFrameIndex;
18000 }
18001 }
18002
18003 if ( element == SHEET_ATK && getAttackTooltipLines(player.playernum, attackHoverTextInfo, 6, buf, valueBuf)
18004 || (element == SHEET_RGN_MP && !isInsectoidENRegen) || element == SHEET_WGT )
18005 {
18006 // extra number display - line 6
18007 hasEntryInfoLines = true;
18008 currentHeight += padyMid;
18009 auto entry = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
18010 ++currentTextFieldIndex;
18011 entry->setDisabled(false);
18012 switch ( element )
18013 {
18014 case SHEET_ATK:
18015 break;
18016 case SHEET_AC:
18017 break;
18018 case SHEET_POW:
18019 break;
18020 case SHEET_RES:
18021 break;
18022 case SHEET_RGN_MP:
18023 {
18024 if ( isAutomatonHTRegen )
18025 {
18026 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_plain_display").c_str());
18027 real_t regen = (static_cast<real_t>(Entity::getManaRegenInterval(player.entity, *stats[player.playernum], true)) / TICKS_PER_SECOND50);
18028 if ( stats[player.playernum]->HUNGER <= 300 )
18029 {
18030 regen /= 6; // degrade faster
18031 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_ht_per_second_format").c_str(), -1, regen);
18032 }
18033 else if ( stats[player.playernum]->HUNGER > 1200 )
18034 {
18035 if ( stats[player.playernum]->MP / static_cast<real_t>(std::max(1, stats[player.playernum]->MAXMP)) <= 0.5 )
18036 {
18037 regen /= 4; // increase faster at < 50% mana
18038 }
18039 else
18040 {
18041 regen /= 2; // increase less faster at > 50% mana
18042 }
18043 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_ht_per_second_format").c_str(), 1, regen);
18044 }
18045 else if ( stats[player.playernum]->HUNGER > 300 )
18046 {
18047 // normal manaRegenInterval 300-1200 hunger.
18048 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_ht_per_second_format").c_str(), 1, regen);
18049 }
18050 }
18051 else
18052 {
18053 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_rgn_plain_display").c_str());
18054 real_t regen = (static_cast<real_t>(Entity::getManaRegenInterval(player.entity, *stats[player.playernum], true)) / TICKS_PER_SECOND50);
18055 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_rgn_mp_per_second_format").c_str(), regen);
18056 }
18057 }
18058 break;
18059 case SHEET_WGT:
18060 {
18061 int weight = player.movement.getCharacterModifiedWeight();
18062 int equippedWeightTotal = player.movement.getCharacterEquippedWeight();
18063 int equippedWeight = player.movement.getCharacterModifiedWeight(&equippedWeightTotal);
18064 int goldWeightTotal = stats[player.playernum]->getGoldWeight();
18065 int goldWeight = player.movement.getCharacterModifiedWeight(&goldWeightTotal);
18066 Sint32 STR = statGetSTR(stats[player.playernum], player.entity);
18067 Sint32 DEX = statGetDEX(stats[player.playernum], player.entity);
18068 //real_t currentEquippedSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(equippedWeight, STR), DEX);
18069 real_t currentSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(weight, STR), DEX);
18070 real_t noWeightSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(0, STR), DEX);
18071 //real_t goldSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(goldWeight, STR), DEX);
18072 real_t noGoldSpeed = player.movement.getSpeedFactor(player.movement.getWeightRatio(weight - goldWeight, STR), DEX);
18073 real_t maxSpeed = player.movement.getMaximumSpeed();
18074
18075 real_t currentSpeedPercent = 100.0 * currentSpeed / std::fmax(.01, maxSpeed);
18076 //real_t currentEquippedSpeedPercent = 100.0 * currentEquippedSpeed / std::fmax(.01, maxSpeed);
18077 real_t noWeightSpeedPercent = 100.0 * noWeightSpeed / std::fmax(.01, maxSpeed);
18078 //real_t goldSpeedPercent = 100.0 * goldSpeed / std::fmax(.01, maxSpeed);
18079 real_t noGoldSpeedPercent = 100.0 * noGoldSpeed / std::fmax(.01, maxSpeed);
18080
18081 real_t displayValue = (currentSpeedPercent - noWeightSpeedPercent)
18082 - (noGoldSpeedPercent - noWeightSpeedPercent);
18083 if ( displayValue >= 0.001 )
18084 {
18085 // do nothing
18086 }
18087 else if ( displayValue >= 0.0 )
18088 {
18089 displayValue = -.000001; // so there is a negative sign
18090 }
18091
18092 snprintf(buf, sizeof(buf), "%s", getHoverTextString("attributes_wgt_gold_value").c_str());
18093 snprintf(valueBuf, sizeof(valueBuf), getHoverTextString("attributes_wgt_speed_bonus_format").c_str(),
18094 displayValue);
18095 }
18096 break;
18097 default:
18098 break;
18099 }
18100 entry->setText(buf);
18101 entry->setVJustify(Field::justify_t::TOP);
18102
18103 SDL_Rect entryPos = entry->getSize();
18104 entryPos.x = padx + padxMid;
18105 entryPos.y = currentHeight;
18106 entryPos.w = txtPos.w - (padxMid * 2);
18107 entry->setSize(entryPos);
18108 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry6 != entry->getText() )
18109 {
18110 entry->reflowTextToFit(0);
18111 charsheetTooltipCache[player.playernum].textEntries[element].entry6 = entry->getText();
18112 }
18113 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
18114 entry->setSize(entryPos);
18115 entry->setColor(defaultColor);
18116 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
18117 tooltipPos.h = pady1 + currentHeight + pady2;
18118
18119 auto entryValue = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
18120 ++currentTextFieldIndex;
18121 entryValue->setDisabled(false);
18122 int value = 0;
18123 switch ( element )
18124 {
18125 case SHEET_ATK:
18126 break;
18127 case SHEET_AC:
18128 break;
18129 case SHEET_POW:
18130 break;
18131 case SHEET_RES:
18132 break;
18133 case SHEET_RGN:
18134 break;
18135 case SHEET_WGT:
18136 break;
18137 default:
18138 break;
18139 }
18140 entryValue->setColor(hudColors.characterSheetNeutral);
18141 if ( value < 0 )
18142 {
18143 entryValue->setColor(hudColors.characterSheetRed);
18144 }
18145 else if ( value > 0 )
18146 {
18147 entryValue->setColor(hudColors.characterSheetGreen);
18148 }
18149 entryValue->setText(valueBuf);
18150 entryValue->setSize(entry->getSize());
18151 entryValue->setHJustify(Frame::justify_t::LEFT);
18152 entryValue->setVJustify(Field::justify_t::TOP);
18153
18154 if ( element == SHEET_RGN_MP )
18155 {
18156 // special rule here to ignore size of this long line
18157 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][currentTextBackingFrameIndex];
18158 SDL_Rect backingFramePos = entryValue->getSize();
18159 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
18160 entryValue->getTextColor(), entryValue->getOutlineColor());
18161 //longestValue = std::max(longestValue, txtValueGet->getWidth());
18162 backingFramePos.x = backingFramePos.x + backingFramePos.w;
18163 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
18164 //valueSizes[currentTextBackingFrameIndex] = std::make_pair(entryValue, backingFramePos);
18165 txtValueBackingFrame->setDisabled(true);
18166
18167 backingFramePos.w = (int)txtValueGet->getWidth();
18168 backingFramePos.x -= backingFramePos.w;
18169 backingFramePos.x -= 8;
18170 entryValue->setSize(backingFramePos);
18171 ++currentTextBackingFrameIndex;
18172 }
18173 else
18174 {
18175 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][currentTextBackingFrameIndex];
18176 SDL_Rect backingFramePos = entryValue->getSize();
18177 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
18178 entryValue->getTextColor(), entryValue->getOutlineColor());
18179 longestValue = std::max(longestValue, txtValueGet->getWidth());
18180 backingFramePos.x = backingFramePos.x + backingFramePos.w;
18181 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
18182 valueSizes[currentTextBackingFrameIndex] = std::make_pair(entryValue, backingFramePos);
18183 txtValueBackingFrame->setDisabled(false);
18184 ++currentTextBackingFrameIndex;
18185 }
18186 }
18187
18188 if ( element == SHEET_ATK && getAttackTooltipLines(player.playernum, attackHoverTextInfo, 7, buf, valueBuf) )
18189 {
18190 // extra number display - line 7
18191 hasEntryInfoLines = true;
18192 currentHeight += padyMid;
18193 auto entry = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
18194 ++currentTextFieldIndex;
18195 entry->setDisabled(false);
18196 switch ( element )
18197 {
18198 case SHEET_ATK:
18199 break;
18200 case SHEET_AC:
18201 break;
18202 case SHEET_POW:
18203 break;
18204 case SHEET_RES:
18205 break;
18206 case SHEET_RGN:
18207 break;
18208 case SHEET_WGT:
18209 break;
18210 default:
18211 break;
18212 }
18213 entry->setText(buf);
18214 entry->setVJustify(Field::justify_t::TOP);
18215
18216 SDL_Rect entryPos = entry->getSize();
18217 entryPos.x = padx + padxMid;
18218 entryPos.y = currentHeight;
18219 entryPos.w = txtPos.w - (padxMid * 2);
18220 entry->setSize(entryPos);
18221 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry7 != entry->getText() )
18222 {
18223 entry->reflowTextToFit(0);
18224 charsheetTooltipCache[player.playernum].textEntries[element].entry7 = entry->getText();
18225 }
18226 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
18227 entry->setSize(entryPos);
18228 entry->setColor(defaultColor);
18229 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
18230 tooltipPos.h = pady1 + currentHeight + pady2;
18231
18232 auto entryValue = characterSheetTooltipTextFields[player.playernum][currentTextFieldIndex]; assert(entry)(static_cast<void> (0));
18233 ++currentTextFieldIndex;
18234 entryValue->setDisabled(false);
18235 int value = 0;
18236 switch ( element )
18237 {
18238 case SHEET_ATK:
18239 break;
18240 case SHEET_AC:
18241 break;
18242 case SHEET_POW:
18243 break;
18244 case SHEET_RES:
18245 break;
18246 case SHEET_RGN:
18247 break;
18248 case SHEET_WGT:
18249 break;
18250 default:
18251 break;
18252 }
18253 entryValue->setColor(hudColors.characterSheetNeutral);
18254 if ( value < 0 )
18255 {
18256 entryValue->setColor(hudColors.characterSheetRed);
18257 }
18258 else if ( value > 0 )
18259 {
18260 entryValue->setColor(hudColors.characterSheetGreen);
18261 }
18262 entryValue->setText(valueBuf);
18263 entryValue->setSize(entry->getSize());
18264 entryValue->setHJustify(Frame::justify_t::LEFT);
18265 entryValue->setVJustify(Field::justify_t::TOP);
18266
18267 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][currentTextBackingFrameIndex];
18268 SDL_Rect backingFramePos = entryValue->getSize();
18269 auto txtValueGet = Text::get(entryValue->getText(), entryValue->getFont(),
18270 entryValue->getTextColor(), entryValue->getOutlineColor());
18271 longestValue = std::max(longestValue, txtValueGet->getWidth());
18272 backingFramePos.x = backingFramePos.x + backingFramePos.w;
18273 backingFramePos.h = actualFont->height(true) + extraTextHeightForLowerCharacters - 2;
18274 valueSizes[currentTextBackingFrameIndex] = std::make_pair(entryValue, backingFramePos);
18275 txtValueBackingFrame->setDisabled(false);
18276 ++currentTextBackingFrameIndex;
18277 }
18278
18279 for ( int index = 1; index <= NUM_CHARSHEET_TOOLTIP_BACKING_FRAMES; ++index )
18280 {
18281 auto txtValueBackingFrame = characterSheetTooltipTextBackingFrames[player.playernum][index];
18282 if ( txtValueBackingFrame->isDisabled() )
18283 {
18284 continue;
18285 }
18286
18287 if ( valueSizes.find(index) == valueSizes.end() )
18288 {
18289 continue;
18290 }
18291
18292 SDL_Rect valuePos = valueSizes[index].second;
18293 Field* entryValue = valueSizes[index].first;
18294 SDL_Rect entryValuePos = entryValue->getSize();
18295 entryValuePos.x = entryValuePos.x + entryValuePos.w;
18296 entryValuePos.w = (int)longestValue;
18297 entryValuePos.x -= entryValuePos.w;
18298 entryValuePos.x -= 8;
18299 entryValue->setSize(entryValuePos);
18300
18301 valuePos.w = (int)longestValue + 16;
18302 valuePos.x -= (valuePos.w);
18303 valuePos.y -= 3;
18304 valuePos.h += 4;
18305
18306 txtValueBackingFrame->setSize(valuePos);
18307
18308 imageResizeToContainer9x9(txtValueBackingFrame, SDL_Rect{ 0, 0, valuePos.w, valuePos.h }, skillsheetEffectBackgroundImages);
18309 }
18310
18311 if ( !hasEntryInfoLines )
18312 {
18313 currentHeight += padyMid * 2;
18314 }
18315
18316 {
18317 currentHeight += padyMid;
18318
18319 div->pos.x = padx;
18320 div->pos.y = currentHeight;
18321 div->pos.w = txtPos.w;
18322 div->disabled = false;
18323
18324 currentHeight += padyMid;
18325
18326 auto entry = characterSheetTooltipTextFields[player.playernum][15]; assert(entry)(static_cast<void> (0));
18327 entry->setDisabled(false);
18328 char buf[512] = "";
18329
18330 std::string descTextFormatted = "\x1E ";
18331 for ( auto s : descText )
18332 {
18333 descTextFormatted += s;
18334 if ( s == '\n' )
18335 {
18336 descTextFormatted += "\x1E ";
18337 }
18338 }
18339
18340 snprintf(buf, sizeof(buf), "%s", descTextFormatted.c_str());
18341 entry->setText(buf);
18342
18343 SDL_Rect entryPos = entry->getSize();
18344 entryPos.x = padx;
18345 entryPos.y = currentHeight;
18346 entryPos.w = txtPos.w;
18347 entry->setSize(entryPos);
18348 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry8 != entry->getText() )
18349 {
18350 entry->reflowTextToFit(0);
18351 charsheetTooltipCache[player.playernum].textEntries[element].entry8 = entry->getText();
18352 }
18353 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
18354 entry->setSize(entryPos);
18355 if ( element == SHEET_RGN && !(svFlags & SV_FLAG_HUNGER) )
18356 {
18357 entry->setColor(hudColors.itemContextMenuHeadingText);
18358 }
18359 else if ( element == SHEET_RGN_MP && isInsectoidENRegen && !(svFlags & SV_FLAG_HUNGER) )
18360 {
18361 entry->setColor(hudColors.itemContextMenuHeadingText);
18362 }
18363 else
18364 {
18365 entry->setColor(hudColors.characterSheetOffWhiteText);
18366 }
18367 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
18368
18369 currentHeight += padyMid / 4;
18370 tooltipPos.h = pady1 + currentHeight + pady2;
18371 }
18372
18373 tooltipPos.h = pady1 + currentHeight + pady2;
18374 if ( tooltipJustify == PANEL_JUSTIFY_RIGHT )
18375 {
18376 tooltipPos.x = pos.x - tooltipPos.w;
18377 }
18378 else
18379 {
18380 tooltipPos.x = pos.x;
18381 }
18382 tooltipPos.y = pos.y;
18383 tooltipFrame->setSize(tooltipPos);
18384 if ( tooltipPos.y + tooltipPos.h > sheetFrame->getSize().h )
18385 {
18386 // keep on-screen
18387 tooltipPos.y -= ((tooltipPos.y + tooltipPos.h) - sheetFrame->getSize().h);
18388 tooltipFrame->setSize(tooltipPos);
18389 }
18390 imageResizeToContainer9x9(tooltipFrame, SDL_Rect{ 0, 0, tooltipPos.w, tooltipPos.h },
18391 skillsheetEffectBackgroundImages);
18392 }
18393 else if ( element == Player::CharacterSheet_t::SHEET_DUNGEON_FLOOR )
18394 {
18395 const int maxWidth = 260;
18396 const int padx = 16;
18397 const int pady1 = 8;
18398 const int pady2 = 4;
18399 const int padxMid = 4;
18400 const int padyMid = 4;
18401 SDL_Rect tooltipPos = SDL_Rect{ 400, 0, maxWidth, 100 };
18402
18403 std::string descriptionText = mapDisplayNamesDescriptions[map.name].second.c_str();
18404 std::string mapDetailsText = "";
18405 auto mapDetails = Player::Minimap_t::mapDetails;
18406 for ( auto& detail : mapDetails )
18407 {
18408 if ( mapDetailsText != "" )
18409 {
18410 mapDetailsText += '\n';
18411 }
18412 mapDetailsText += "\x1E ";
18413 mapDetailsText += detail.second;
18414 }
18415
18416 if ( strcmp(descriptionText.c_str(), txt->getText()) )
18417 {
18418 txt->setText(descriptionText.c_str());
18419 }
18420 SDL_Rect txtPos = SDL_Rect{ padx, pady1, maxWidth - padx * 2, 80 };
18421 txt->setSize(txtPos);
18422 if ( charsheetTooltipCache[player.playernum].textEntries[element].title != txt->getText() )
18423 {
18424 txt->reflowTextToFit(0);
18425 charsheetTooltipCache[player.playernum].textEntries[element].title = txt->getText();
18426 }
18427 Font* actualFont = Font::get(txt->getFont());
18428 int txtHeight = txt->getNumTextLines() * actualFont->height(true);
18429 txtPos.h = txtHeight + 4;
18430 auto txtGet = Text::get(txt->getLongestLine().c_str(), txt->getFont(),
18431 txt->getTextColor(), txt->getOutlineColor());
18432 txtPos.w = txtGet->getWidth();
18433 txt->setSize(txtPos);
18434
18435 tooltipPos.w = txtGet->getWidth() + padx * 2;
18436
18437 int currentHeight = txtPos.y + txtPos.h - 4;
18438
18439 if ( Player::Minimap_t::mapDetails.size() > 0 )
18440 {
18441 currentHeight += padyMid;
18442 auto entry = tooltipFrame->findField("txt 1"); assert(entry)(static_cast<void> (0));
18443 entry->setDisabled(false);
18444 if ( strcmp(mapDetailsText.c_str(), entry->getText()) )
18445 {
18446 entry->setText(mapDetailsText.c_str());
18447 }
18448
18449 SDL_Rect entryPos = entry->getSize();
18450 entryPos.x = padx + padxMid;
18451 entryPos.y = currentHeight;
18452 entryPos.w = tooltipPos.w - (2 * (entryPos.x));
18453 entry->setSize(entryPos);
18454 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry1 != entry->getText() )
18455 {
18456 entry->reflowTextToFit(0);
18457 charsheetTooltipCache[player.playernum].textEntries[element].entry1 = entry->getText();
18458 }
18459 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + 4;
18460 entry->setSize(entryPos);
18461 entry->setColor(makeColor(255, 0, 255, 255));
18462 currentHeight = std::max(entryPos.y + entryPos.h, 0);
18463 }
18464
18465 if ( Player::Minimap_t::mapDetails.size() > 0 )
18466 {
18467 tooltipPos.h = pady1 + currentHeight + pady2;
18468 }
18469 else
18470 {
18471 tooltipPos.h = pady1 + txtPos.h + pady2;
18472 }
18473 if ( tooltipJustify == PANEL_JUSTIFY_RIGHT )
18474 {
18475 tooltipPos.x = pos.x - tooltipPos.w;
18476 }
18477 else
18478 {
18479 tooltipPos.x = pos.x;
18480 }
18481 tooltipPos.y = pos.y;
18482
18483 tooltipFrame->setSize(tooltipPos);
18484 imageResizeToContainer9x9(tooltipFrame, SDL_Rect{ 0, 0, tooltipPos.w, tooltipPos.h },
18485 skillsheetEffectBackgroundImages);
18486 }
18487 else if ( element == Player::CharacterSheet_t::SHEET_GOLD )
18488 {
18489 const int maxWidth = 200;
18490 const int padx = 16;
18491 const int pady1 = 8;
18492 const int pady2 = 8;
18493 const int padyMid = 4;
18494 const int padxMid = 4;
18495 SDL_Rect tooltipPos = SDL_Rect{ 400, 0, maxWidth, 100 };
18496 bool usingMouse = !inputs.getVirtualMouse(player.playernum)->lastMovementFromController;
18497 if ( strcmp(getHoverTextString("gold_mouse").c_str(), txt->getText()) )
18498 {
18499 txt->setText(getHoverTextString("gold_mouse").c_str());
18500 }
18501
18502 SDL_Rect txtPos = SDL_Rect{ padx, pady1, maxWidth - padx * 2, 80 };
18503 txt->setSize(txtPos);
18504 if ( charsheetTooltipCache[player.playernum].textEntries[element].title != txt->getText() )
18505 {
18506 txt->reflowTextToFit(0);
18507 charsheetTooltipCache[player.playernum].textEntries[element].title = txt->getText();
18508 }
18509 Font* actualFont = Font::get(txt->getFont());
18510 int txtHeight = txt->getNumTextLines() * actualFont->height(true);
18511 txtPos.h = txtHeight + padyMid;
18512 auto txtGet = Text::get(txt->getLongestLine().c_str(), txt->getFont(),
18513 txt->getTextColor(), txt->getOutlineColor());
18514 txtPos.w = txtGet->getWidth();
18515 txt->setSize(txtPos);
18516
18517 tooltipPos.w = txtPos.w + padx * 2;
18518 tooltipPos.h = pady1 + txtPos.h + pady2;
18519
18520 if ( tooltipJustify == PANEL_JUSTIFY_RIGHT )
18521 {
18522 tooltipPos.x = pos.x - tooltipPos.w;
18523 }
18524 else
18525 {
18526 tooltipPos.x = pos.x;
18527 }
18528 tooltipPos.y = pos.y;
18529
18530 tooltipFrame->setSize(tooltipPos);
18531 imageResizeToContainer9x9(tooltipFrame, SDL_Rect{ 0, 0, tooltipPos.w, tooltipPos.h },
18532 skillsheetEffectBackgroundImages);
18533 }
18534 else if ( element == Player::CharacterSheet_t::SHEET_CHAR_RACE_SEX )
18535 {
18536 auto tooltipTopLeft = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
18537 tooltipTopLeft->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TL_Blue_00.png";
18538 auto tooltipTop = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP].c_str());
18539 tooltipTop->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_T_Blue_00.png";
18540 auto tooltipTopRight = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
18541 tooltipTopRight->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TR_Blue_00.png";
18542 imageSetWidthHeight9x9(tooltipFrame, skillsheetEffectBackgroundImages);
18543
18544 int maxWidth = 260;
18545 int minWidth = 0;
18546
18547 const int padx = 16;
18548 const int pady1 = 8;
18549 const int pady2 = 4;
18550 const int padxMid = 4;
18551 const int padyMid = 8;
18552 SDL_Rect tooltipPos = SDL_Rect{ 400, 0, maxWidth, 100 };
18553
18554 Monster race = HUMAN;
18555 if ( stats[player.playernum]->appearance == 0 && stats[player.playernum]->playerRace != RACE_HUMAN )
18556 {
18557 race = getMonsterFromPlayerRace(stats[player.playernum]->playerRace);
18558 }
18559 Monster modifiedRace = stats[player.playernum]->type;
18560 if ( arachnophobia_filter )
18561 {
18562 if ( modifiedRace == SPIDER )
18563 {
18564 modifiedRace = CRAB;
18565 }
18566 if ( race == SPIDER )
18567 {
18568 race = CRAB;
18569 }
18570 }
18571
18572 char titleBuf[64];
18573 std::string titleText = getHoverTextString("race_title_normal");
18574 if ( players[player.playernum]->entity )
18575 {
18576 if ( players[player.playernum]->entity->effectShapeshift != NOTHING )
18577 {
18578 titleText = getHoverTextString("race_title_shapeshift");
18579 }
18580 else if ( race != modifiedRace )
18581 {
18582 titleText = getHoverTextString("race_title_polymorph");
18583 }
18584 }
18585
18586 txt->setText(titleText.c_str());
18587 SDL_Rect txtPos = SDL_Rect{ padx, pady1 - 2, maxWidth - padx * 2, 80 };
18588 txt->setSize(txtPos);
18589 if ( charsheetTooltipCache[player.playernum].textEntries[element].title != txt->getText() )
18590 {
18591 txt->reflowTextToFit(0);
18592 charsheetTooltipCache[player.playernum].textEntries[element].title = txt->getText();
18593 }
18594 txt->setColor(hudColors.characterSheetHeadingText);
18595 Font* actualFont = Font::get(txt->getFont());
18596 int txtHeight = txt->getNumTextLines() * actualFont->height(true);
18597 txtPos.h = txtHeight + 4;
18598 auto txtGet = Text::get(txt->getLongestLine().c_str(), txt->getFont(),
18599 txt->getTextColor(), txt->getOutlineColor());
18600 txtPos.w = txtGet->getWidth();
18601 txtPos.w = std::max(minWidth - padx * 2, txtPos.w);
18602 txt->setSize(txtPos);
18603
18604 tooltipPos.w = (txtPos.w + padx * 2);
18605
18606 unsigned int longestValue = 0;
18607 std::map<int, std::pair<Field*, SDL_Rect>> valueSizes;
18608
18609 int currentHeight = txtPos.y + (actualFont->height(true) * 1) + 2;
18610 const int extraTextHeightForLowerCharacters = 4;
18611 currentHeight += padyMid;
18612
18613 if ( raceTooltip )
18614 {
18615 raceTooltip->setDisabled(false);
18616 MainMenu::RaceDescriptions::update_details_text(*raceTooltip, stats[player.playernum]);
18617
18618 SDL_Rect raceTooltipPos = raceTooltip->getSize();
18619 raceTooltipPos.x = txtPos.x + padxMid;
18620 raceTooltipPos.y = currentHeight;
18621 raceTooltipPos.w = 272;
18622 int heightOffset = 0;
18623 if ( auto details_text = raceTooltip->findField("details") )
18624 {
18625 SDL_Rect pos = details_text->getSize();
18626 if ( auto actualFont = Font::get(details_text->getFont()) )
18627 {
18628 const int numlines = details_text->getNumTextLines();
18629 const int pad = details_text->getPaddingPerLine();
18630 const int actualHeight = actualFont->height(true);
18631 pos.h = 0;
18632 for ( int line = 0; line < numlines; ++line )
18633 {
18634 pos.h += actualHeight + pad;// +details_text->getIndividualLinePadding(line);
18635 heightOffset += details_text->getIndividualLinePadding(line);
18636 }
18637 }
18638 raceTooltipPos.h = pos.h + pos.y + extraTextHeightForLowerCharacters;
18639 details_text->setSize(pos);
18640 }
18641 if ( auto details_text_right = raceTooltip->findField("details_right") )
18642 {
18643 SDL_Rect pos = details_text_right->getSize();
18644 if ( auto actualFont = Font::get(details_text_right->getFont()) )
18645 {
18646 const int numlines = details_text_right->getNumTextLines();
18647 const int pad = details_text_right->getPaddingPerLine();
18648 const int actualHeight = actualFont->height(true);
18649 pos.h = 0;
18650 for ( int line = 0; line < numlines; ++line )
18651 {
18652 pos.h += actualHeight + pad;// +details_text_right->getIndividualLinePadding(line);
18653 }
18654 }
18655
18656 details_text_right->setSize(pos);
18657 }
18658 raceTooltip->setSize(raceTooltipPos);
18659 tooltipPos.w = raceTooltipPos.w + padxMid * 2;
18660 currentHeight = std::max(raceTooltipPos.y + raceTooltipPos.h - extraTextHeightForLowerCharacters + heightOffset, 0);
18661 }
18662
18663 tooltipPos.h = pady1 + currentHeight + pady2;
18664 if ( tooltipJustify == PANEL_JUSTIFY_RIGHT )
18665 {
18666 tooltipPos.x = pos.x - tooltipPos.w;
18667 }
18668 else
18669 {
18670 tooltipPos.x = pos.x;
18671 }
18672 tooltipPos.y = pos.y;
18673 if ( tooltipPos.y + tooltipPos.h > sheetFrame->getSize().h )
18674 {
18675 // keep on-screen
18676 tooltipPos.y -= ((tooltipPos.y + tooltipPos.h) - sheetFrame->getSize().h);
18677 tooltipFrame->setSize(tooltipPos);
18678 }
18679 tooltipFrame->setSize(tooltipPos);
18680 imageResizeToContainer9x9(tooltipFrame, SDL_Rect{ 0, 0, tooltipPos.w, tooltipPos.h },
18681 skillsheetEffectBackgroundImages);
18682 }
18683 else if ( element == Player::CharacterSheet_t::SHEET_CHAR_CLASS )
18684 {
18685 auto tooltipTopLeft = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
18686 tooltipTopLeft->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TL_Blue_00.png";
18687 auto tooltipTop = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP].c_str());
18688 tooltipTop->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_T_Blue_00.png";
18689 auto tooltipTopRight = tooltipFrame->findImage(skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
18690 tooltipTopRight->path = "*#images/ui/CharSheet/HUD_CharSheet_Tooltip_TR_Blue_00.png";
18691 imageSetWidthHeight9x9(tooltipFrame, skillsheetEffectBackgroundImages);
18692
18693 int maxWidth = 260;
18694 int minWidth = 0;
18695 if ( getHoverTextString("stat_growth_min_tooltip_width") != defaultString )
18696 {
18697 minWidth = std::max(0, std::stoi(getHoverTextString("stat_growth_min_tooltip_width")));
18698 }
18699 if ( getHoverTextString("stat_growth_max_tooltip_width") != defaultString )
18700 {
18701 maxWidth = std::max(0, std::stoi(getHoverTextString("stat_growth_max_tooltip_width")));
18702 }
18703
18704 const int padx = 16;
18705 const int pady1 = 8;
18706 const int pady2 = 4;
18707 const int padxMid = 4;
18708 const int padyMid = 8;
18709 SDL_Rect tooltipPos = SDL_Rect{ 400, 0, maxWidth, 100 };
18710
18711 Monster race = HUMAN;
18712 if ( stats[player.playernum]->appearance == 0 && stats[player.playernum]->playerRace != RACE_HUMAN )
18713 {
18714 race = getMonsterFromPlayerRace(stats[player.playernum]->playerRace);
18715 }
18716 Monster modifiedRace = stats[player.playernum]->type;
18717 if ( arachnophobia_filter )
18718 {
18719 if ( modifiedRace == SPIDER )
18720 {
18721 modifiedRace = CRAB;
18722 }
18723 if ( race == SPIDER )
18724 {
18725 race = CRAB;
18726 }
18727 }
18728
18729 char titleBuf[64];
18730 if ( player.entity && player.entity->effectShapeshift != 0 )
18731 {
18732 txt->setText(getHoverTextString("class_title_shapeshift").c_str());
18733 }
18734 else
18735 {
18736 txt->setText(getHoverTextString("class_title").c_str());
18737 }
18738 SDL_Rect txtPos = SDL_Rect{ padx, pady1 - 2, maxWidth - padx * 2, 80 };
18739 txt->setSize(txtPos);
18740 if ( charsheetTooltipCache[player.playernum].textEntries[element].title != txt->getText() )
18741 {
18742 txt->reflowTextToFit(0);
18743 charsheetTooltipCache[player.playernum].textEntries[element].title = txt->getText();
18744 }
18745 txt->setColor(hudColors.characterSheetHeadingText);
18746 Font* actualFont = Font::get(txt->getFont());
18747 int txtHeight = txt->getNumTextLines() * actualFont->height(true);
18748 txtPos.h = txtHeight + 4;
18749 auto txtGet = Text::get(txt->getLongestLine().c_str(), txt->getFont(),
18750 txt->getTextColor(), txt->getOutlineColor());
18751 txtPos.w = txtGet->getWidth();
18752 txtPos.w = std::max(minWidth - padx * 2, txtPos.w);
18753 txt->setSize(txtPos);
18754
18755 tooltipPos.w = (txtPos.w + padx * 2);
18756
18757 std::map<int, std::pair<Field*, SDL_Rect>> valueSizes;
18758
18759 int currentHeight = txtPos.y + (actualFont->height(true) * 1) + 2;
18760 const int extraTextHeightForLowerCharacters = 4;
18761 currentHeight += padyMid;
18762
18763 if ( classTooltip )
18764 {
18765 classTooltip->setDisabled(false);
18766 auto statGrowths = classTooltip->findFrame("stat growths");
18767 if ( player.entity )
18768 {
18769 MainMenu::ClassDescriptions::update_stat_growths(*statGrowths, client_classes[player.playernum], player.entity->effectShapeshift);
18770 }
18771 else
18772 {
18773 MainMenu::ClassDescriptions::update_stat_growths(*statGrowths, client_classes[player.playernum], 0);
18774 }
18775
18776 SDL_Rect classTooltipPos = classTooltip->getSize();
18777 classTooltipPos.w = statGrowths->getSize().w;
18778 classTooltipPos.h = statGrowths->getSize().h;
18779 classTooltipPos.x = tooltipPos.w / 2 - classTooltipPos.w / 2;
18780 classTooltipPos.y = currentHeight;
18781 classTooltip->setSize(classTooltipPos);
18782
18783 currentHeight += classTooltipPos.h;
18784
18785 std::string descText = "";
18786 descText = getHoverTextString("stat_growth_info");
18787
18788 {
18789 currentHeight += padyMid;
18790
18791 div->pos.x = padx;
18792 div->pos.y = currentHeight;
18793 div->pos.w = txtPos.w;
18794 div->disabled = false;
18795
18796 currentHeight += padyMid;
18797
18798 auto entry = characterSheetTooltipTextFields[player.playernum][1]; assert(entry)(static_cast<void> (0));
18799 entry->setDisabled(false);
18800 char buf[512] = "";
18801
18802 std::string descTextFormatted = "\x1E ";
18803 for ( auto s : descText )
18804 {
18805 descTextFormatted += s;
18806 if ( s == '\n' )
18807 {
18808 descTextFormatted += "\x1E ";
18809 }
18810 }
18811
18812 snprintf(buf, sizeof(buf), "%s", descTextFormatted.c_str());
18813 entry->setText(buf);
18814
18815 SDL_Rect entryPos = entry->getSize();
18816 entryPos.x = padx;
18817 entryPos.y = currentHeight;
18818 entryPos.w = txtPos.w;
18819 entry->setSize(entryPos);
18820 if ( charsheetTooltipCache[player.playernum].textEntries[element].entry1 != entry->getText() )
18821 {
18822 entry->reflowTextToFit(0);
18823 charsheetTooltipCache[player.playernum].textEntries[element].entry1 = entry->getText();
18824 }
18825 entryPos.h = actualFont->height(true) * entry->getNumTextLines() + extraTextHeightForLowerCharacters;
18826 entry->setSize(entryPos);
18827 entry->setColor(hudColors.characterSheetOffWhiteText);
18828 currentHeight = std::max(entryPos.y + entryPos.h - extraTextHeightForLowerCharacters, 0);
18829
18830 currentHeight += padyMid / 4;
18831 tooltipPos.h = pady1 + currentHeight + pady2;
18832 }
18833 }
18834
18835 tooltipPos.h = pady1 + currentHeight + pady2;
18836 if ( tooltipJustify == PANEL_JUSTIFY_RIGHT )
18837 {
18838 tooltipPos.x = pos.x - tooltipPos.w;
18839 }
18840 else
18841 {
18842 tooltipPos.x = pos.x;
18843 }
18844 tooltipPos.y = pos.y;
18845 if ( tooltipPos.y + tooltipPos.h > sheetFrame->getSize().h )
18846 {
18847 // keep on-screen
18848 tooltipPos.y -= ((tooltipPos.y + tooltipPos.h) - sheetFrame->getSize().h);
18849 tooltipFrame->setSize(tooltipPos);
18850 }
18851 tooltipFrame->setSize(tooltipPos);
18852 imageResizeToContainer9x9(tooltipFrame, SDL_Rect{ 0, 0, tooltipPos.w, tooltipPos.h },
18853 skillsheetEffectBackgroundImages);
18854 }
18855 else if ( element == Player::CharacterSheet_t::SHEET_TIMER )
18856 {
18857 const int maxWidth = 240;
18858 const int padx = 16;
18859 const int pady1 = 8;
18860 const int pady2 = 8;
18861 const int padyMid = 4;
18862 const int padxMid = 4;
18863 SDL_Rect tooltipPos = SDL_Rect{ 400, 0, maxWidth, 100 };
18864 bool usingMouse = !inputs.getVirtualMouse(player.playernum)->lastMovementFromController;
18865
18866 if ( player.characterSheet.showGameTimerAlways )
18867 {
18868 std::string tooltiptxt = getHoverTextString("game_timer_mouse") + getHoverTextString("game_timer_unpin");
18869 if ( strcmp(tooltiptxt.c_str(), txt->getText()) )
18870 {
18871 txt->setText(tooltiptxt.c_str());
18872 }
18873 }
18874 else
18875 {
18876 std::string tooltiptxt = getHoverTextString("game_timer_mouse") + getHoverTextString("game_timer_pin");
18877 if ( strcmp(tooltiptxt.c_str(), txt->getText()) )
18878 {
18879 txt->setText(tooltiptxt.c_str());
18880 }
18881 }
18882
18883 int currentHeight = padyMid;
18884
18885 SDL_Rect txtPos = SDL_Rect{ padx, pady1, maxWidth - padx * 2, 80 };
18886 txt->setSize(txtPos);
18887 if ( charsheetTooltipCache[player.playernum].textEntries[element].title != txt->getText() )
18888 {
18889 txt->reflowTextToFit(0);
18890 charsheetTooltipCache[player.playernum].textEntries[element].title = txt->getText();
18891 }
18892 Font* actualFont = Font::get(txt->getFont());
18893 int txtHeight = txt->getNumTextLines() * actualFont->height(true);
18894 txtPos.h = txtHeight + padyMid;
18895 auto txtGet = Text::get(txt->getLongestLine().c_str(), txt->getFont(),
18896 txt->getTextColor(), txt->getOutlineColor());
18897 txtPos.w = txtGet->getWidth();
18898 txt->setSize(txtPos);
18899
18900 currentHeight = std::max(currentHeight, txtPos.h);
18901
18902 tooltipPos.w = txtPos.w + padx * 2;
18903 tooltipPos.h = pady1 + currentHeight + pady2;
18904 if ( tooltipJustify == PANEL_JUSTIFY_RIGHT )
18905 {
18906 tooltipPos.x = pos.x - tooltipPos.w;
18907 }
18908 else
18909 {
18910 tooltipPos.x = pos.x;
18911 }
18912 tooltipPos.y = pos.y;
18913
18914 tooltipFrame->setSize(tooltipPos);
18915 imageResizeToContainer9x9(tooltipFrame, SDL_Rect{ 0, 0, tooltipPos.w, tooltipPos.h },
18916 skillsheetEffectBackgroundImages);
18917 }
18918}
18919
18920void Player::CharacterSheet_t::updateCharacterInfo()
18921{
18922 auto characterInfoFrame = sheetFrame->findFrame("character info");
18923 assert(characterInfoFrame)(static_cast<void> (0));
18924 auto characterInnerFrame = characterInfoFrame->findFrame("character info inner frame");
18925 assert(characterInnerFrame)(static_cast<void> (0));
18926
18927 bool enableTooltips = !player.GUI.isDropdownActive() && !player.GUI.dropdownMenu.bClosedThisTick;
18928 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor
18929 && inputs.hasController(player.playernum)
18930 && !Input::inputs[player.playernum].binary("MenuConfirm") )
18931 {
18932 enableTooltips = false;
18933 }
18934
18935 bool bCompactView = player.bUseCompactGUIHeight();
18936
18937 char buf[32] = "";
18938 if ( auto name = characterInnerFrame->findField("character name text") )
18939 {
18940 if ( strcmp(stats[player.playernum]->name, name->getText()) )
18941 {
18942 name->setText(stats[player.playernum]->name);
18943 }
18944 }
18945 Field* className = characterInnerFrame->findField("character class text");
18946 int classNameWidth = 0;
18947 if ( className )
18948 {
18949 std::string classname = playerClassLangEntry(client_classes[player.playernum], player.playernum);
18950 if ( !classname.empty() )
18951 {
18952 capitalizeString(classname);
18953 if ( strcmp(classname.c_str(), className->getText()) )
18954 {
18955 className->setText(classname.c_str());
18956 }
18957 }
18958 if ( client_classes[player.playernum] >= CLASS_CONJURER && client_classes[player.playernum] <= CLASS_BREWER )
18959 {
18960 className->setTextColor(hudColors.characterDLC1ClassText);
18961 }
18962 else if ( client_classes[player.playernum] >= CLASS_MACHINIST && client_classes[player.playernum] <= CLASS_HUNTER )
18963 {
18964 className->setTextColor(hudColors.characterDLC2ClassText);
18965 }
18966 else
18967 {
18968 className->setTextColor(hudColors.characterBaseClassText);
18969 }
18970 if ( auto textGet = Text::get(className->getText(), className->getFont(),
18971 className->getTextColor(), className->getOutlineColor()) )
18972 {
18973 classNameWidth = textGet->getWidth();
18974 }
18975 }
18976 Field* charLevel = characterInnerFrame->findField("character level text");
18977 int charLevelWidth = 0;
18978 if ( charLevel )
18979 {
18980 snprintf(buf, sizeof(buf), Language::get(4051), stats[player.playernum]->LVL);
18981 if ( strcmp(buf, charLevel->getText()) )
18982 {
18983 charLevel->setText(buf);
18984 }
18985 if ( auto textGet = Text::get(charLevel->getText(), charLevel->getFont(),
18986 charLevel->getTextColor(), charLevel->getOutlineColor()) )
18987 {
18988 charLevelWidth = textGet->getWidth();
18989 }
18990 }
18991 if ( className && charLevel )
18992 {
18993 SDL_Rect classNamePos = className->getSize();
18994 SDL_Rect charLevelPos = charLevel->getSize();
18995 const int padding = 24;
18996 const int startX = 8;
18997 const int fieldWidth = 190;
18998 const int totalWidth = charLevelWidth + padding / 2 + classNameWidth;
18999 charLevelPos.x = startX + fieldWidth / 2 - totalWidth / 2;
19000 charLevelPos.w = charLevelWidth;
19001 classNamePos.x = charLevelPos.x + charLevelPos.w + padding / 2 - 4; // -4 centres it nicely somehow, not sure why
19002 classNamePos.w = classNameWidth;
19003 charLevel->setSize(charLevelPos);
19004 className->setSize(classNamePos);
19005 //messagePlayer(0, "%d | %d", charLevelPos.x - startX, 198 - (classNamePos.x + classNamePos.w));
19006
19007 if ( selectedElement == SHEET_CHAR_CLASS && enableTooltips )
19008 {
19009 SDL_Rect tooltipPos = characterInfoFrame->getSize();
19010 tooltipPos.y -= 4;
19011 //tooltipPos.y += raceText->getSize().y;
19012 Player::PanelJustify_t tooltipJustify = PANEL_JUSTIFY_RIGHT;
19013 if ( (panelJustify == PANEL_JUSTIFY_LEFT && !bCompactView) || (panelJustify == PANEL_JUSTIFY_RIGHT && bCompactView) )
19014 {
19015 tooltipJustify = PANEL_JUSTIFY_LEFT;
19016 tooltipPos.x += tooltipPos.w;
19017 }
19018 if ( bCompactView )
19019 {
19020 tooltipPos.y = 0;
19021 tooltipPos.x += (tooltipJustify == PANEL_JUSTIFY_LEFT) ? 6 : -6;
19022 }
19023 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19024 }
19025 }
19026 if ( auto raceText = characterInnerFrame->findField("character race text") )
19027 {
19028 Monster type = stats[player.playernum]->type;
19029 std::string appearance = "";
19030 bool aestheticOnly = false;
19031 if ( player.entity )
19032 {
19033 if ( player.entity->effectPolymorph == NOTHING && stats[player.playernum]->playerRace > RACE_HUMAN )
19034 {
19035 if ( stats[player.playernum]->appearance != 0 )
19036 {
19037 aestheticOnly = true;
19038 appearance = Language::get(4068);
19039 type = player.entity->getMonsterFromPlayerRace(stats[player.playernum]->playerRace);
19040 }
19041 }
19042 }
19043 std::string race = getMonsterLocalizedName(type).c_str();
19044 capitalizeString(race);
19045 if ( type == HUMAN )
19046 {
19047 appearance = Language::get(20 + stats[player.playernum]->appearance % NUMAPPEARANCES18);
19048 capitalizeString(appearance);
19049 }
19050 bool centerIconAndText = false;
19051 if ( appearance != "" )
19052 {
19053 if ( aestheticOnly )
19054 {
19055 snprintf(buf, sizeof(buf), "%s %s", appearance.c_str(), race.c_str()); // 'guised skeleton'
19056 }
19057 else
19058 {
19059 snprintf(buf, sizeof(buf), "%s %s", race.c_str(), appearance.c_str()); // 'human gloomforge'
19060 }
19061 centerIconAndText = true;
19062 }
19063 else
19064 {
19065 snprintf(buf, sizeof(buf), "%s", race.c_str());
19066 }
19067
19068 if ( strcmp(buf, raceText->getText()) )
19069 {
19070 raceText->setText(buf);
19071 }
19072 int width = 0;
19073 if ( auto textGet = Text::get(raceText->getText(), raceText->getFont(),
19074 raceText->getTextColor(), raceText->getOutlineColor()) )
19075 {
19076 width = textGet->getWidth();
19077 }
19078
19079 if ( auto sexImg = characterInnerFrame->findImage("character sex img") )
19080 {
19081 int offsetx = 0;
19082 if ( stats[player.playernum]->sex == sex_t::MALE )
19083 {
19084 if ( type == AUTOMATON )
19085 {
19086 sexImg->path = "*#images/ui/CharSheet/HUD_CharSheet_Sex_AutomatonM_02.png";
19087 }
19088 else
19089 {
19090 sexImg->path = "*#images/ui/CharSheet/HUD_CharSheet_Sex_M_02.png";
19091 static ConsoleVariable<int> cvar_sexoffset("/sexoffsetx", -1);
19092 offsetx = *cvar_sexoffset;
19093 }
19094 }
19095 else if ( stats[player.playernum]->sex == sex_t::FEMALE )
19096 {
19097 if ( type == AUTOMATON )
19098 {
19099 sexImg->path = "*#images/ui/CharSheet/HUD_CharSheet_Sex_AutomatonF_02.png";
19100 }
19101 else
19102 {
19103 sexImg->path = "*#images/ui/CharSheet/HUD_CharSheet_Sex_F_02.png";
19104 }
19105 }
19106 if ( auto imgGet = Image::get(sexImg->path.c_str()) )
19107 {
19108 sexImg->pos.w = (int)imgGet->getWidth();
19109 sexImg->pos.h = (int)imgGet->getHeight();
19110 }
19111
19112 SDL_Rect raceTextPos = raceText->getSize();
19113 raceTextPos.x = 4;
19114 if ( centerIconAndText )
19115 {
19116 raceTextPos.x = 4 + (((sexImg->pos.w + 4)) / 2) + offsetx;
19117 }
19118 if ( raceTextPos.x % 2 == 1 )
19119 {
19120 --raceTextPos.x;
19121 }
19122 raceText->setSize(raceTextPos);
19123
19124 sexImg->pos.x = 16;
19125 if ( centerIconAndText )
19126 {
19127 sexImg->pos.x = raceText->getSize().x + raceText->getSize().w / 2 - width / 2;
19128 sexImg->pos.x -= (sexImg->pos.w + 4) + (offsetx * 2);
19129 }
19130 if ( sexImg->pos.x % 2 == 1 )
19131 {
19132 --sexImg->pos.x;
19133 }
19134 sexImg->pos.y = raceText->getSize().y + raceText->getSize().h / 2 - sexImg->pos.h / 2;
19135 }
19136
19137 if ( selectedElement == SHEET_CHAR_RACE_SEX && enableTooltips )
19138 {
19139 SDL_Rect tooltipPos = characterInfoFrame->getSize();
19140 tooltipPos.y -= 4;
19141 //tooltipPos.y += raceText->getSize().y;
19142 Player::PanelJustify_t tooltipJustify = PANEL_JUSTIFY_RIGHT;
19143 if ( (panelJustify == PANEL_JUSTIFY_LEFT && !bCompactView) || (panelJustify == PANEL_JUSTIFY_RIGHT && bCompactView) )
19144 {
19145 tooltipJustify = PANEL_JUSTIFY_LEFT;
19146 tooltipPos.x += tooltipPos.w;
19147 }
19148 if ( bCompactView )
19149 {
19150 tooltipPos.y = 0;
19151 tooltipPos.x += (tooltipJustify == PANEL_JUSTIFY_LEFT) ? 6 : -6;
19152 }
19153 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19154 }
19155 }
19156 if ( auto floorFrame = sheetFrame->findFrame("dungeon floor frame") )
19157 {
19158 if ( auto floorLevelText = floorFrame->findField("dungeon level text") )
19159 {
19160 snprintf(buf, sizeof(buf), Language::get(4052), currentlevel);
19161 if ( strcmp(buf, floorLevelText->getText()) )
19162 {
19163 floorLevelText->setText(buf);
19164 charsheetTooltipCache[player.playernum].manualUpdate = true;
19165 }
19166 }
19167 if ( auto floorNameText = floorFrame->findField("dungeon name text") )
19168 {
19169 if ( mapDisplayNamesDescriptions.find(map.name) != mapDisplayNamesDescriptions.end() )
19170 {
19171 if ( strcmp(mapDisplayNamesDescriptions[map.name].first.c_str(), floorNameText->getText()) )
19172 {
19173 floorNameText->setText(mapDisplayNamesDescriptions[map.name].first.c_str());
19174 charsheetTooltipCache[player.playernum].manualUpdate = true;
19175 }
19176
19177 if ( selectedElement == SHEET_DUNGEON_FLOOR && enableTooltips )
19178 {
19179 SDL_Rect tooltipPos = characterInfoFrame->getSize();
19180 tooltipPos.y = floorFrame->getSize().y;
19181 Player::PanelJustify_t tooltipJustify = PANEL_JUSTIFY_RIGHT;
19182 if ( (panelJustify == PANEL_JUSTIFY_LEFT && !bCompactView) || (panelJustify == PANEL_JUSTIFY_RIGHT && bCompactView) )
19183 {
19184 tooltipJustify = PANEL_JUSTIFY_LEFT;
19185 tooltipPos.x += tooltipPos.w;
19186 }
19187 if ( bCompactView )
19188 {
19189 tooltipPos.y = 0;
19190 tooltipPos.x += (tooltipJustify == PANEL_JUSTIFY_LEFT) ? 6 : -6;
19191 }
19192 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19193 }
19194 }
19195 else
19196 {
19197 floorNameText->setText(map.name);
19198 }
19199 }
19200 }
19201
19202 if ( auto gold = characterInnerFrame->findField("gold text") )
19203 {
19204 snprintf(buf, sizeof(buf), "%d", stats[player.playernum]->GOLD);
19205 gold->setText(buf);
19206 if ( selectedElement == SHEET_GOLD )
19207 {
19208 if ( Input::inputs[player.playernum].binary("MenuRightClick")
19209 && player.bControlEnabled && !gamePaused
19210 && !player.usingCommand()
19211 && player.GUI.activeModule == Player::GUI_t::MODULE_CHARACTERSHEET
19212 && !player.GUI.isDropdownActive() )
19213 {
19214 player.GUI.dropdownMenu.open("drop_gold");
19215 }
19216 else if ( (!inputs.getVirtualMouse(player.playernum)->draw_cursor
19217 && inputs.hasController(player.playernum)
19218 && Input::inputs[player.playernum].binaryToggle("MenuConfirm")
19219 )
19220 && player.GUI.activeModule == Player::GUI_t::MODULE_CHARACTERSHEET
19221 && !player.GUI.isDropdownActive()
19222 && !player.usingCommand()
19223 && player.bControlEnabled && !gamePaused )
19224 {
19225 Input::inputs[player.playernum].consumeBinaryToggle("MenuConfirm");
19226 player.GUI.dropdownMenu.open("drop_gold");
19227 player.GUI.dropdownMenu.dropDownToggleClick = true;
19228 SDL_Rect dropdownPos = characterInfoFrame->getSize();
19229 dropdownPos.y += gold->getSize().y + gold->getSize().h / 2;
19230 if ( auto interactMenuTop = player.GUI.dropdownMenu.dropdownFrame->findImage("interact top background") )
19231 {
19232 // 10px is slot half height, move by 1.5 slots, minus the top interact text height
19233 dropdownPos.y -= (interactMenuTop->pos.h + (3 * 10) + 4);
19234 }
19235 if ( !player.GUI.dropdownMenu.getDropDownAlignRight("drop_gold") )
19236 {
19237 player.GUI.dropdownMenu.dropDownX = dropdownPos.x;
19238 }
19239 else
19240 {
19241 player.GUI.dropdownMenu.dropDownX = dropdownPos.x + dropdownPos.w;
19242 }
19243 player.GUI.dropdownMenu.dropDownX += player.camera_virtualx1();
19244 player.GUI.dropdownMenu.dropDownY = dropdownPos.y + player.camera_virtualy1();
19245 }
19246 if ( enableTooltips && !inputs.getVirtualMouse(player.playernum)->lastMovementFromController )
19247 {
19248 SDL_Rect tooltipPos = characterInfoFrame->getSize();
19249 tooltipPos.y += gold->getSize().y;
19250 tooltipPos.y -= 6;
19251 Player::PanelJustify_t tooltipJustify = PANEL_JUSTIFY_RIGHT;
19252 if ( (panelJustify == PANEL_JUSTIFY_LEFT && !bCompactView) || (panelJustify == PANEL_JUSTIFY_RIGHT && bCompactView) )
19253 {
19254 tooltipJustify = PANEL_JUSTIFY_LEFT;
19255 tooltipPos.x += tooltipPos.w;
19256 }
19257 if ( bCompactView )
19258 {
19259 tooltipPos.x += (tooltipJustify == PANEL_JUSTIFY_LEFT) ? 6 : -6;
19260 }
19261 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19262 }
19263 }
19264 }
19265}
19266
19267void Player::CharacterSheet_t::updateStats()
19268{
19269 auto characterInfoFrame = sheetFrame->findFrame("character info");
19270 assert(characterInfoFrame)(static_cast<void> (0));
19271 auto characterInnerFrame = characterInfoFrame->findFrame("character info inner frame");
19272 assert(characterInnerFrame)(static_cast<void> (0));
19273 auto statsFrame = sheetFrame->findFrame("stats");
19274 assert(statsFrame)(static_cast<void> (0));
19275
19276 auto statsPos = statsFrame->getSize();
19277 //statsPos.x = sheetFrame->getSize().w - statsPos.w;
19278 statsFrame->setSize(statsPos);
19279
19280 auto statsInnerFrame = statsFrame->findFrame("stats inner frame");
19281 assert(statsInnerFrame)(static_cast<void> (0));
19282
19283 const int rightAlignPosX = 0;
19284 const int leftAlignPosX = 10;
19285 auto statsInnerPos = statsInnerFrame->getSize();
19286 statsInnerPos.x = rightAlignPosX;
19287 /*if ( keystatus[SDLK_I] && enableDebugKeys )
19288 {
19289 statsInnerPos.x = leftAlignPosX;
19290 }*/
19291 statsInnerFrame->setSize(statsInnerPos);
19292 Button* strButton = statsInnerFrame->findButton("str button");
19293 Button* dexButton = statsInnerFrame->findButton("dex button");
19294 Button* conButton = statsInnerFrame->findButton("con button");
19295 Button* intButton = statsInnerFrame->findButton("int button");
19296 Button* perButton = statsInnerFrame->findButton("per button");
19297 Button* chrButton = statsInnerFrame->findButton("chr button");
19298
19299 bool bCompactView = player.bUseCompactGUIHeight();
19300
19301 bool enableTooltips = !player.GUI.isDropdownActive() && !player.GUI.dropdownMenu.bClosedThisTick;
19302 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor
19303 && inputs.hasController(player.playernum)
19304 && !Input::inputs[player.playernum].binary("MenuConfirm") )
19305 {
19306 enableTooltips = false;
19307 }
19308 char buf[32] = "";
19309 if ( auto field = statsInnerFrame->findField("str text stat") )
19310 {
19311 snprintf(buf, sizeof(buf), "%d", stats[player.playernum]->STR);
19312 if ( strcmp(buf, field->getText()) )
19313 {
19314 field->setText(buf);
19315 charsheetTooltipCache[player.playernum].manualUpdate = true;
19316 }
19317 field->setColor(hudColors.characterSheetNeutral);
19318
19319 Sint32 modifiedStat = statGetSTR(stats[player.playernum], players[player.playernum]->entity);
19320 if ( auto modifiedField = statsInnerFrame->findField("str text modified") )
19321 {
19322 modifiedField->setColor(hudColors.characterSheetNeutral);
19323 modifiedField->setDisabled(true);
19324 snprintf(buf, sizeof(buf), "%d", modifiedStat);
19325 if ( strcmp(buf, modifiedField->getText()) )
19326 {
19327 modifiedField->setText(buf);
19328 charsheetTooltipCache[player.playernum].manualUpdate = true;
19329 }
19330 if ( modifiedStat > stats[player.playernum]->STR )
19331 {
19332 modifiedField->setColor(hudColors.characterSheetGreen);
19333 modifiedField->setDisabled(false);
19334 }
19335 else if ( modifiedStat < stats[player.playernum]->STR )
19336 {
19337 modifiedField->setColor(hudColors.characterSheetRed);
19338 modifiedField->setDisabled(false);
19339 }
19340 }
19341 if ( selectedElement == SHEET_STR && enableTooltips )
19342 {
19343 SDL_Rect tooltipPos = statsFrame->getSize();
19344 tooltipPos.y += statsInnerFrame->getSize().y;
19345 Player::PanelJustify_t tooltipJustify = panelJustify;
19346 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19347 {
19348 tooltipPos.x += tooltipPos.w;
19349 }
19350 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19351 }
19352 }
19353 if ( auto field = statsInnerFrame->findField("dex text stat") )
19354 {
19355 snprintf(buf, sizeof(buf), "%d", stats[player.playernum]->DEX);
19356 if ( strcmp(buf, field->getText()) )
19357 {
19358 field->setText(buf);
19359 charsheetTooltipCache[player.playernum].manualUpdate = true;
19360 }
19361 field->setColor(hudColors.characterSheetNeutral);
19362
19363 Sint32 modifiedStat = statGetDEX(stats[player.playernum], players[player.playernum]->entity);
19364 if ( auto modifiedField = statsInnerFrame->findField("dex text modified") )
19365 {
19366 modifiedField->setColor(hudColors.characterSheetNeutral);
19367 modifiedField->setDisabled(true);
19368 snprintf(buf, sizeof(buf), "%d", modifiedStat);
19369 if ( strcmp(buf, modifiedField->getText()) )
19370 {
19371 modifiedField->setText(buf);
19372 charsheetTooltipCache[player.playernum].manualUpdate = true;
19373 }
19374 if ( modifiedStat > stats[player.playernum]->DEX )
19375 {
19376 modifiedField->setColor(hudColors.characterSheetGreen);
19377 modifiedField->setDisabled(false);
19378 }
19379 else if ( modifiedStat < stats[player.playernum]->DEX )
19380 {
19381 modifiedField->setColor(hudColors.characterSheetRed);
19382 modifiedField->setDisabled(false);
19383 }
19384 }
19385 if ( selectedElement == SHEET_DEX && enableTooltips )
19386 {
19387 SDL_Rect tooltipPos = statsFrame->getSize();
19388 tooltipPos.y += statsInnerFrame->getSize().y;
19389 Player::PanelJustify_t tooltipJustify = panelJustify;
19390 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19391 {
19392 tooltipPos.x += tooltipPos.w;
19393 }
19394 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19395 }
19396 }
19397 if ( auto field = statsInnerFrame->findField("con text stat") )
19398 {
19399 snprintf(buf, sizeof(buf), "%d", stats[player.playernum]->CON);
19400 if ( strcmp(buf, field->getText()) )
19401 {
19402 field->setText(buf);
19403 charsheetTooltipCache[player.playernum].manualUpdate = true;
19404 }
19405 field->setColor(hudColors.characterSheetNeutral);
19406
19407 Sint32 modifiedStat = statGetCON(stats[player.playernum], players[player.playernum]->entity);
19408 if ( auto modifiedField = statsInnerFrame->findField("con text modified") )
19409 {
19410 modifiedField->setColor(hudColors.characterSheetNeutral);
19411 modifiedField->setDisabled(true);
19412 snprintf(buf, sizeof(buf), "%d", modifiedStat);
19413 if ( strcmp(buf, modifiedField->getText()) )
19414 {
19415 modifiedField->setText(buf);
19416 charsheetTooltipCache[player.playernum].manualUpdate = true;
19417 }
19418 if ( modifiedStat > stats[player.playernum]->CON )
19419 {
19420 modifiedField->setColor(hudColors.characterSheetGreen);
19421 modifiedField->setDisabled(false);
19422 }
19423 else if ( modifiedStat < stats[player.playernum]->CON )
19424 {
19425 modifiedField->setColor(hudColors.characterSheetRed);
19426 modifiedField->setDisabled(false);
19427 }
19428 }
19429 if ( selectedElement == SHEET_CON && enableTooltips )
19430 {
19431 SDL_Rect tooltipPos = statsFrame->getSize();
19432 tooltipPos.y += statsInnerFrame->getSize().y;
19433 Player::PanelJustify_t tooltipJustify = panelJustify;
19434 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19435 {
19436 tooltipPos.x += tooltipPos.w;
19437 }
19438 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19439 }
19440 }
19441 if ( auto field = statsInnerFrame->findField("int text stat") )
19442 {
19443 snprintf(buf, sizeof(buf), "%d", stats[player.playernum]->INT);
19444 if ( strcmp(buf, field->getText()) )
19445 {
19446 field->setText(buf);
19447 charsheetTooltipCache[player.playernum].manualUpdate = true;
19448 }
19449 field->setColor(hudColors.characterSheetNeutral);
19450
19451 Sint32 modifiedStat = statGetINT(stats[player.playernum], players[player.playernum]->entity);
19452 if ( auto modifiedField = statsInnerFrame->findField("int text modified") )
19453 {
19454 modifiedField->setColor(hudColors.characterSheetNeutral);
19455 modifiedField->setDisabled(true);
19456 snprintf(buf, sizeof(buf), "%d", modifiedStat);
19457 if ( strcmp(buf, modifiedField->getText()) )
19458 {
19459 modifiedField->setText(buf);
19460 charsheetTooltipCache[player.playernum].manualUpdate = true;
19461 }
19462 if ( modifiedStat > stats[player.playernum]->INT )
19463 {
19464 modifiedField->setColor(hudColors.characterSheetGreen);
19465 modifiedField->setDisabled(false);
19466 }
19467 else if ( modifiedStat < stats[player.playernum]->INT )
19468 {
19469 modifiedField->setColor(hudColors.characterSheetRed);
19470 modifiedField->setDisabled(false);
19471 }
19472 }
19473 if ( selectedElement == SHEET_INT && enableTooltips )
19474 {
19475 SDL_Rect tooltipPos = statsFrame->getSize();
19476 tooltipPos.y += statsInnerFrame->getSize().y;
19477 Player::PanelJustify_t tooltipJustify = panelJustify;
19478 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19479 {
19480 tooltipPos.x += tooltipPos.w;
19481 }
19482 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19483 }
19484 }
19485 if ( auto field = statsInnerFrame->findField("per text stat") )
19486 {
19487 snprintf(buf, sizeof(buf), "%d", stats[player.playernum]->PER);
19488 if ( strcmp(buf, field->getText()) )
19489 {
19490 field->setText(buf);
19491 charsheetTooltipCache[player.playernum].manualUpdate = true;
19492 }
19493 field->setColor(hudColors.characterSheetNeutral);
19494
19495 Sint32 modifiedStat = statGetPER(stats[player.playernum], players[player.playernum]->entity);
19496 if ( auto modifiedField = statsInnerFrame->findField("per text modified") )
19497 {
19498 modifiedField->setColor(hudColors.characterSheetNeutral);
19499 modifiedField->setDisabled(true);
19500 snprintf(buf, sizeof(buf), "%d", modifiedStat);
19501 if ( strcmp(buf, modifiedField->getText()) )
19502 {
19503 modifiedField->setText(buf);
19504 charsheetTooltipCache[player.playernum].manualUpdate = true;
19505 }
19506 if ( modifiedStat > stats[player.playernum]->PER )
19507 {
19508 modifiedField->setColor(hudColors.characterSheetGreen);
19509 modifiedField->setDisabled(false);
19510 }
19511 else if ( modifiedStat < stats[player.playernum]->PER )
19512 {
19513 modifiedField->setColor(hudColors.characterSheetRed);
19514 modifiedField->setDisabled(false);
19515 }
19516 }
19517 if ( selectedElement == SHEET_PER && enableTooltips )
19518 {
19519 SDL_Rect tooltipPos = statsFrame->getSize();
19520 tooltipPos.y += statsInnerFrame->getSize().y;
19521 Player::PanelJustify_t tooltipJustify = panelJustify;
19522 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19523 {
19524 tooltipPos.x += tooltipPos.w;
19525 }
19526 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19527 }
19528 }
19529 if ( auto field = statsInnerFrame->findField("chr text stat") )
19530 {
19531 snprintf(buf, sizeof(buf), "%d", stats[player.playernum]->CHR);
19532 if ( strcmp(buf, field->getText()) )
19533 {
19534 field->setText(buf);
19535 charsheetTooltipCache[player.playernum].manualUpdate = true;
19536 }
19537 field->setColor(hudColors.characterSheetNeutral);
19538
19539 Sint32 modifiedStat = statGetCHR(stats[player.playernum], players[player.playernum]->entity);
19540 if ( auto modifiedField = statsInnerFrame->findField("chr text modified") )
19541 {
19542 modifiedField->setColor(hudColors.characterSheetNeutral);
19543 modifiedField->setDisabled(true);
19544 snprintf(buf, sizeof(buf), "%d", modifiedStat);
19545 if ( strcmp(buf, modifiedField->getText()) )
19546 {
19547 modifiedField->setText(buf);
19548 charsheetTooltipCache[player.playernum].manualUpdate = true;
19549 }
19550 if ( modifiedStat > stats[player.playernum]->CHR )
19551 {
19552 modifiedField->setColor(hudColors.characterSheetGreen);
19553 modifiedField->setDisabled(false);
19554 }
19555 else if ( modifiedStat < stats[player.playernum]->CHR )
19556 {
19557 modifiedField->setColor(hudColors.characterSheetRed);
19558 modifiedField->setDisabled(false);
19559 }
19560 }
19561 if ( selectedElement == SHEET_CHR && enableTooltips )
19562 {
19563 SDL_Rect tooltipPos = statsFrame->getSize();
19564 tooltipPos.y += statsInnerFrame->getSize().y;
19565 Player::PanelJustify_t tooltipJustify = panelJustify;
19566 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19567 {
19568 tooltipPos.x += tooltipPos.w;
19569 }
19570 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19571 }
19572 }
19573}
19574
19575void Player::CharacterSheet_t::updateAttributes()
19576{
19577 auto attributesFrame = sheetFrame->findFrame("attributes");
19578 assert(attributesFrame)(static_cast<void> (0));
19579
19580 auto attributesPos = attributesFrame->getSize();
19581 //attributesPos.x = sheetFrame->getSize().w - attributesPos.w;
19582 attributesFrame->setSize(attributesPos);
19583
19584 auto attributesInnerFrame = attributesFrame->findFrame("attributes inner frame");
19585 assert(attributesInnerFrame)(static_cast<void> (0));
19586
19587 const int rightAlignPosX = 0;
19588 const int leftAlignPosX = 10;
19589 auto attributesInnerPos = attributesInnerFrame->getSize();
19590 attributesInnerPos.x = rightAlignPosX;
19591 /*if ( keystatus[SDLK_I] )
19592 {
19593 attributesInnerPos.x = leftAlignPosX;
19594 }*/
19595 attributesInnerFrame->setSize(attributesInnerPos);
19596
19597 bool bCompactView = player.bUseCompactGUIHeight();
19598
19599 bool enableTooltips = !player.GUI.isDropdownActive() && !player.GUI.dropdownMenu.bClosedThisTick;
19600 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor
19601 && inputs.hasController(player.playernum)
19602 && !Input::inputs[player.playernum].binary("MenuConfirm") )
19603 {
19604 enableTooltips = false;
19605 }
19606
19607 char buf[32] = "";
19608
19609 if ( auto field = attributesInnerFrame->findField("atk text stat") )
19610 {
19611 AttackHoverText_t atkHoverText;
19612 Sint32 displayedATK = displayAttackPower(player.playernum, atkHoverText);
19613 if ( atkHoverText.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_MAGICSTAFF
19614 || atkHoverText.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_TOOL
19615 || atkHoverText.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_TOOL_TRAP
19616 || atkHoverText.hoverType == AttackHoverText_t::ATK_HOVER_TYPE_DEFAULT )
19617 {
19618 snprintf(buf, sizeof(buf), "-");
19619 }
19620 else
19621 {
19622 snprintf(buf, sizeof(buf), "%d", displayedATK);
19623 }
19624 if ( strcmp(buf, field->getText()) )
19625 {
19626 field->setText(buf);
19627 charsheetTooltipCache[player.playernum].manualUpdate = true;
19628 }
19629 field->setColor(hudColors.characterSheetNeutral);
19630
19631 if ( selectedElement == SHEET_ATK && enableTooltips )
19632 {
19633 SDL_Rect tooltipPos = attributesFrame->getSize();
19634 tooltipPos.y += attributesInnerFrame->getSize().y;
19635 Player::PanelJustify_t tooltipJustify = panelJustify;
19636 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19637 {
19638 tooltipPos.x += tooltipPos.w;
19639 }
19640 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19641 }
19642 }
19643
19644 if ( auto field = attributesInnerFrame->findField("ac text stat") )
19645 {
19646 snprintf(buf, sizeof(buf), "%d", AC(stats[player.playernum]));
19647 if ( strcmp(buf, field->getText()) )
19648 {
19649 field->setText(buf);
19650 charsheetTooltipCache[player.playernum].manualUpdate = true;
19651 }
19652 field->setColor(hudColors.characterSheetNeutral);
19653
19654 if ( selectedElement == SHEET_AC && enableTooltips )
19655 {
19656 SDL_Rect tooltipPos = attributesFrame->getSize();
19657 tooltipPos.y += attributesInnerFrame->getSize().y;
19658 Player::PanelJustify_t tooltipJustify = panelJustify;
19659 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19660 {
19661 tooltipPos.x += tooltipPos.w;
19662 }
19663 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19664 }
19665 }
19666
19667 if ( auto field = attributesInnerFrame->findField("pwr text stat") )
19668 {
19669 real_t spellPower = (getBonusFromCasterOfSpellElement(player.entity, stats[player.playernum], nullptr, SPELL_NONE) * 100.0) + 100.0;
19670 snprintf(buf, sizeof(buf), "%.f%%", spellPower);
19671 if ( strcmp(buf, field->getText()) )
19672 {
19673 field->setText(buf);
19674 charsheetTooltipCache[player.playernum].manualUpdate = true;
19675 }
19676 field->setColor(hudColors.characterSheetNeutral);
19677
19678 if ( selectedElement == SHEET_POW && enableTooltips )
19679 {
19680 SDL_Rect tooltipPos = attributesFrame->getSize();
19681 tooltipPos.y += attributesInnerFrame->getSize().y;
19682 Player::PanelJustify_t tooltipJustify = panelJustify;
19683 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19684 {
19685 tooltipPos.x += tooltipPos.w;
19686 }
19687 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19688 }
19689 }
19690
19691 if ( auto field = attributesInnerFrame->findField("res text stat") )
19692 {
19693 real_t resistance = 100.0 * Entity::getDamageTableMultiplier(player.entity, *stats[player.playernum], DAMAGE_TABLE_MAGIC);
19694 resistance /= (Entity::getMagicResistance(stats[player.playernum]) + 1);
19695 resistance = -(resistance - 100.0);
19696 snprintf(buf, sizeof(buf), "%d%%", (int)resistance);
19697 if ( strcmp(buf, field->getText()) )
19698 {
19699 field->setText(buf);
19700 charsheetTooltipCache[player.playernum].manualUpdate = true;
19701 }
19702 field->setColor(hudColors.characterSheetNeutral);
19703 if ( resistance > 0.01 )
19704 {
19705 field->setColor(hudColors.characterSheetGreen);
19706 }
19707 else if ( resistance < -0.01 )
19708 {
19709 field->setColor(hudColors.characterSheetRed);
19710 }
19711
19712 if ( selectedElement == SHEET_RES && enableTooltips )
19713 {
19714 SDL_Rect tooltipPos = attributesFrame->getSize();
19715 tooltipPos.y += attributesInnerFrame->getSize().y;
19716 Player::PanelJustify_t tooltipJustify = panelJustify;
19717 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19718 {
19719 tooltipPos.x += tooltipPos.w;
19720 }
19721 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19722 }
19723 }
19724
19725 if ( auto field = attributesInnerFrame->findField("regen text hp") )
19726 {
19727 field->setColor(hudColors.characterSheetNeutral);
19728 Uint32 color = hudColors.characterSheetNeutral;
19729 getDisplayedHPRegen(players[player.playernum]->entity, *stats[player.playernum], &color, buf);
19730 if ( strcmp(buf, field->getText()) )
19731 {
19732 field->setText(buf);
19733 charsheetTooltipCache[player.playernum].manualUpdate = true;
19734 }
19735 field->setColor(color);
19736
19737 if ( selectedElement == SHEET_RGN && enableTooltips )
19738 {
19739 SDL_Rect tooltipPos = attributesFrame->getSize();
19740 tooltipPos.y += attributesInnerFrame->getSize().y;
19741 Player::PanelJustify_t tooltipJustify = panelJustify;
19742 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19743 {
19744 tooltipPos.x += tooltipPos.w;
19745 }
19746 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19747 }
19748 }
19749
19750 if ( auto field = attributesInnerFrame->findField("regen text mp") )
19751 {
19752 field->setColor(hudColors.characterSheetNeutral);
19753 Uint32 color = hudColors.characterSheetNeutral;
19754 getDisplayedMPRegen(players[player.playernum]->entity, *stats[player.playernum], &color, buf);
19755 if ( strcmp(buf, field->getText()) )
19756 {
19757 field->setText(buf);
19758 charsheetTooltipCache[player.playernum].manualUpdate = true;
19759 }
19760 field->setColor(color);
19761
19762 if ( selectedElement == SHEET_RGN_MP && enableTooltips )
19763 {
19764 SDL_Rect tooltipPos = attributesFrame->getSize();
19765 tooltipPos.y += attributesInnerFrame->getSize().y;
19766 Player::PanelJustify_t tooltipJustify = panelJustify;
19767 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19768 {
19769 tooltipPos.x += tooltipPos.w;
19770 }
19771 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19772 }
19773 }
19774
19775
19776 if ( auto field = attributesInnerFrame->findField("weight text stat") )
19777 {
19778 Sint32 weight = 0;
19779 for ( node_t* node = stats[player.playernum]->inventory.first; node != NULL__null; node = node->next )
19780 {
19781 Item* item = (Item*)node->element;
19782 if ( item )
19783 {
19784 weight += item->getWeight();
19785 }
19786 }
19787 weight += stats[player.playernum]->getGoldWeight();
19788 snprintf(buf, sizeof(buf), "%d", weight);
19789 if ( strcmp(buf, field->getText()) )
19790 {
19791 field->setText(buf);
19792 charsheetTooltipCache[player.playernum].manualUpdate = true;
19793 }
19794 field->setColor(hudColors.characterSheetNeutral);
19795
19796 if ( selectedElement == SHEET_WGT && enableTooltips )
19797 {
19798 SDL_Rect tooltipPos = attributesFrame->getSize();
19799 tooltipPos.y += attributesInnerFrame->getSize().y;
19800 Player::PanelJustify_t tooltipJustify = panelJustify;
19801 if ( panelJustify == PANEL_JUSTIFY_LEFT )
19802 {
19803 tooltipPos.x += tooltipPos.w;
19804 }
19805 updateCharacterSheetTooltip(selectedElement, tooltipPos, tooltipJustify);
19806 }
19807 }
19808}
19809
19810void Player::Hotbar_t::processHotbar()
19811{
19812#ifndef NINTENDO
19813 if ( inputs.hasController(player.playernum) )
19814 {
19815 useHotbarFaceMenu = playerSettings[multiplayer ? 0 : player.playernum].gamepad_facehotbar;
19816 }
19817 else if ( inputs.bPlayerUsingKeyboardControl(player.playernum) )
19818 {
19819 useHotbarFaceMenu = false;
19820 }
19821#else
19822 useHotbarFaceMenu = playerSettings[multiplayer ? 0 : player.playernum].gamepad_facehotbar;
19823#endif // NINTENDO
19824
19825 if ( !hotbarFrame )
19826 {
19827 char name[32];
19828 snprintf(name, sizeof(name), "player hotbar %d", player.playernum);
19829 hotbarFrame = player.hud.hudFrame->addFrame(name);
19830 hotbarFrame->setHollow(true);
19831 hotbarFrame->setBorder(0);
19832 hotbarFrame->setOwner(player.playernum);
19833 hotbarFrame->setInheritParentFrameOpacity(false);
19834 createHotbar(player.playernum);
19835 }
19836 hotbarFrame->setSize(SDL_Rect{ 0, 0,
19837 players[player.playernum]->camera_virtualWidth(),
19838 players[player.playernum]->camera_virtualHeight() });
19839
19840 if ( gamePaused || nohud || !players[player.playernum]->isLocalPlayer() )
19841 {
19842 // hide
19843 hotbarFrame->setDisabled(true);
19844 return;
19845 }
19846 else
19847 {
19848 hotbarFrame->setDisabled(false);
19849 }
19850
19851 updateHotbar();
19852}
19853
19854void Player::Inventory_t::Appraisal_t::updateAppraisalAnim()
19855{
19856 /*if ( current_item == 0 )
19857 {
19858 animAppraisal = 0.0;
19859 return;
19860 }*/
19861 real_t fpsScale = getFPSScale(60.0);
19862 real_t scale = PI3.14159265358979323846 / 40;
19863 animAppraisal += fpsScale * (scale);
19864 if ( animAppraisal >= 4 * PI3.14159265358979323846 )
19865 {
19866 animAppraisal -= 4 * PI3.14159265358979323846;
19867 }
19868
19869 if ( itemNotifyUpdatedThisTick == 0 )
19870 {
19871 itemNotifyUpdatedThisTick = ticks;
19872 itemNotifyAnimState = 0;
19873 }
19874 if ( (itemNotifyUpdatedThisTick != ticks) && (ticks - itemNotifyUpdatedThisTick) % (TICKS_PER_SECOND50 / 5) == 0 )
19875 {
19876 if ( itemNotifyUpdatedThisTick != ticks )
19877 {
19878 ++itemNotifyAnimState;
19879 itemNotifyUpdatedThisTick = ticks;
19880 }
19881 if ( itemNotifyAnimState > 2 )
19882 {
19883 itemNotifyAnimState = 0;
19884 }
19885 }
19886}
19887
19888void drawClockwiseSquareMesh(const char* texture, float lerp, SDL_Rect rect, Uint32 color) {
19889 auto image = Image::get(texture);
19890 image->drawClockwise(lerp, nullptr, rect,
19891 SDL_Rect{0, 0, Frame::virtualScreenX, Frame::virtualScreenY}, color);
19892}
19893
19894void drawUnidentifiedItemEffectHotbarCallback(const Widget& widget, SDL_Rect rect)
19895{
19896 const int player = widget.getOwner();
19897 auto& appraisal = players[player]->inventoryUI.appraisal;
19898 if ( appraisal.animStartTick == ticks )
19899 {
19900 return;
19901 }
19902
19903 const Frame* parent = static_cast<const Frame*>(widget.getParent());
19904 {
19905 SDL_Rect drawRect = rect;
19906 drawRect.x += 4;
19907 drawRect.y += 4;
19908 drawRect.w -= 6;
19909 drawRect.h -= 6;
19910 drawRect.x += drawRect.w / 2;
19911 drawRect.y += drawRect.h / 2;
19912 real_t opacity = 192;
19913 if ( parent && parent->getOpacity() < 100.0 )
19914 {
19915 opacity *= parent->getOpacity() / 100.0;
19916 }
19917 const auto& appraisal = players[player]->inventoryUI.appraisal;
19918 drawClockwiseSquareMesh("images/ui/HUD/hotbar/Appraisal_Icon_OutlineHotbar.png",
19919 (appraisal.timermax - appraisal.timer) / (float)appraisal.timermax,
19920 drawRect, makeColor(255, 255, 255, opacity));
19921 }
19922
19923 auto drawMesh = [](real_t x, real_t y, real_t size, SDL_Rect rect, Uint32 color) {
19924 auto image = Image::get("images/ui/Inventory/Appraisal_Icon.png");
19925 const real_t sx = rect.w * size;
19926 const real_t sy = rect.h * size;
19927 image->drawColor(nullptr, SDL_Rect{(int)x, (int)y, (int)sx, (int)sy},
19928 SDL_Rect{0, 0, Frame::virtualScreenX, Frame::virtualScreenY}, color);
19929 };
19930 {
19931 const int imgSize = 26;
19932 SDL_Rect drawRect = rect;
19933 drawRect.x += 4 + (rect.w - 6) / 2 - imgSize / 2;
19934 drawRect.y += 4 + (rect.h - 6) / 2 - imgSize / 2;
19935 int offsetSize = 7;
19936 int offsetx = offsetSize * cos(std::min(4 * PI3.14159265358979323846, appraisal.animAppraisal));
19937 int offsety = offsetSize * sin(std::min(4 * PI3.14159265358979323846, appraisal.animAppraisal));
19938
19939 drawRect.w = imgSize;
19940 drawRect.h = imgSize;
19941
19942 real_t opacity = 92 + 160 * fabs(sin(std::min(2 * PI3.14159265358979323846, appraisal.animAppraisal) / 2));
19943 if ( parent && parent->getOpacity() < 100.0 )
19944 {
19945 opacity *= parent->getOpacity() / 100.0;
19946 }
19947 drawMesh(drawRect.x + offsetx, drawRect.y + offsety,
19948 (real_t)1.0, drawRect, makeColor(255, 255, 255, opacity));
19949 }
19950}
19951
19952void drawUnidentifiedItemEffectCallback(const Widget& widget, SDL_Rect rect)
19953{
19954 const int player = widget.getOwner();
19955 auto& appraisal = players[player]->inventoryUI.appraisal;
19956 if ( appraisal.animStartTick == ticks )
19957 {
19958 return;
19959 }
19960
19961 const Frame* parent = static_cast<const Frame*>(widget.getParent());
19962 {
19963 SDL_Rect drawRect = rect;
19964 drawRect.x += 2;
19965 drawRect.y += 2;
19966 drawRect.w -= 2;
19967 drawRect.h -= 2;
19968 drawRect.x += drawRect.w / 2;
19969 drawRect.y += drawRect.h / 2;
19970 real_t opacity = 192;
19971 if ( parent && parent->getOpacity() < 100.0 )
19972 {
19973 opacity *= parent->getOpacity() / 100.0;
19974 }
19975 drawClockwiseSquareMesh("images/ui/Inventory/Appraisal_Icon_Outline.png",
19976 (appraisal.timermax - appraisal.timer) / (float)appraisal.timermax,
19977 drawRect, makeColor(255, 255, 255, opacity));
19978 }
19979
19980 auto drawMesh = [](real_t x, real_t y, real_t size, SDL_Rect rect, Uint32 color) {
19981 auto image = Image::get("images/ui/Inventory/Appraisal_Icon.png");
19982 const real_t sx = rect.w * size;
19983 const real_t sy = rect.h * size;
19984 image->drawColor(nullptr, SDL_Rect{(int)x, (int)y, (int)sx, (int)sy},
19985 SDL_Rect{0, 0, Frame::virtualScreenX, Frame::virtualScreenY}, color);
19986 };
19987 {
19988 const int imgSize = 26;
19989 SDL_Rect drawRect = rect;
19990 drawRect.x += 4 + (rect.w - 6) / 2 - imgSize / 2;
19991 drawRect.y += 4 + (rect.h - 6) / 2 - imgSize / 2;
19992 int offsetSize = 7;
19993 int offsetx = offsetSize * cos(std::min(4 * PI3.14159265358979323846, appraisal.animAppraisal));
19994 int offsety = offsetSize * sin(std::min(4 * PI3.14159265358979323846, appraisal.animAppraisal));
19995
19996 drawRect.w = imgSize;
19997 drawRect.h = imgSize;
19998
19999 real_t opacity = 92 + 160 * fabs(sin(std::min(2 * PI3.14159265358979323846, appraisal.animAppraisal) / 2));
20000 if ( parent && parent->getOpacity() < 100.0 )
20001 {
20002 opacity *= parent->getOpacity() / 100.0;
20003 }
20004 drawMesh(drawRect.x + offsetx, drawRect.y + offsety,
20005 (real_t)1.0, drawRect, makeColor(255, 255, 255, opacity));
20006 }
20007}
20008
20009void createPlayerInventorySlotFrameElements(Frame* slotFrame)
20010{
20011 const SDL_Rect slotSize = SDL_Rect{ 0, 0, slotFrame->getSize().w, slotFrame->getSize().h };
20012 SDL_Rect coloredBackgroundPos = SDL_Rect{ slotSize.x + 2, slotSize.y + 2, slotSize.w - 2, slotSize.h - 2 };
20013
20014 auto beatitudeFrame = slotFrame->addFrame("beatitude status frame"); // covers unidentified status as well
20015 beatitudeFrame->setSize(slotSize);
20016 beatitudeFrame->setHollow(true);
20017 beatitudeFrame->setDisabled(true);
20018 beatitudeFrame->addImage(coloredBackgroundPos, 0xFFFFFFFF, "images/system/white.png", "beatitude status bg");
20019
20020 auto brokenStatusFrame = slotFrame->addFrame("broken status frame");
20021 brokenStatusFrame->setSize(slotSize);
20022 brokenStatusFrame->setHollow(true);
20023 brokenStatusFrame->setDisabled(true);
20024 brokenStatusFrame->addImage(coloredBackgroundPos, makeColor( 160, 160, 160, 64), "images/system/white.png", "broken status bg");
20025
20026 auto itemSpriteFrame = slotFrame->addFrame("item sprite frame");
20027
20028 // cut off the slot 2px borders
20029 SDL_Rect itemSpriteBorder = { slotSize.x + 2, slotSize.y + 2, slotFrame->getSize().w - 2, slotFrame->getSize().h - 2 };
20030 const int itemSpriteSize = players[slotFrame->getOwner()]->inventoryUI.getItemSpriteSize();
20031 const int alignOffset = (itemSpriteBorder.w - itemSpriteSize) / 2; // align the item sprite within the box by this offset to center
20032 itemSpriteBorder.x += alignOffset;
20033 itemSpriteBorder.y += alignOffset;
20034 itemSpriteBorder.w = itemSpriteSize;
20035 itemSpriteBorder.h = itemSpriteSize;
20036
20037 itemSpriteFrame->setSize(SDL_Rect{ itemSpriteBorder.x, itemSpriteBorder.y,
20038 itemSpriteBorder.w, itemSpriteBorder.h });
20039 itemSpriteFrame->setHollow(true);
20040 itemSpriteFrame->setDisabled(true);
20041 SDL_Rect imgPos{ 0, 0, itemSpriteFrame->getSize().w, itemSpriteFrame->getSize().h };
20042 auto img = itemSpriteFrame->addImage(imgPos, 0xFFFFFFFF, "images/system/white.png", "item sprite img");
20043 img->outline = false;
20044 img->outlineColor = 0;
20045 auto iconLabelBgImg = itemSpriteFrame->addImage(SDL_Rect{ 0, 0, 16, 16 }, 0xFFFFFFFF,
20046 "images/ui/Inventory/Icon_Label_Backing_00.png", "icon label bg img");
20047 iconLabelBgImg->disabled = true;
20048
20049 auto iconLabelImg = itemSpriteFrame->addImage(SDL_Rect{ 0, 0, 16, 16 }, 0xFFFFFFFF,
20050 "", "icon label img");
20051 iconLabelImg->disabled = true;
20052
20053 auto unusableFrame = slotFrame->addFrame("unusable item frame");
20054 unusableFrame->setSize(slotSize);
20055 unusableFrame->setHollow(true);
20056 unusableFrame->setDisabled(true);
20057 unusableFrame->addImage(coloredBackgroundPos, makeColor( 64, 64, 64, 144), "images/system/white.png", "unusable item bg");
20058
20059 auto appraisalFrame = slotFrame->addFrame("appraisal frame");
20060 appraisalFrame->setSize(slotSize);
20061 appraisalFrame->setHollow(true);
20062 appraisalFrame->setDisabled(true);
20063 appraisalFrame->addImage(SDL_Rect{ 4, 4, 6, 14 }, 0xFFFFFFFF,
20064 "images/ui/Inventory/tooltips/ExclamationAnim00.png", "new notif img");
20065
20066 static const char* qtyfont = "fonts/pixel_maz.ttf#32#2";
20067 auto quantityFrame = slotFrame->addFrame("quantity frame");
20068 quantityFrame->setSize(slotSize);
20069 quantityFrame->setHollow(true);
20070 Field* qtyText = quantityFrame->addField("quantity text", 32);
20071 qtyText->setFont(qtyfont);
20072 qtyText->setColor(0xffffffff);
20073 qtyText->setHJustify(Field::justify_t::BOTTOM);
20074 qtyText->setVJustify(Field::justify_t::RIGHT);
20075 qtyText->setText("10");
20076 qtyText->setSize(SDL_Rect{ 0, 6, quantityFrame->getSize().w, quantityFrame->getSize().h });
20077
20078 auto equippedIconFrame = slotFrame->addFrame("equipped icon frame");
20079 equippedIconFrame->setSize(slotSize);
20080 equippedIconFrame->setHollow(true);
20081 SDL_Rect equippedImgPos = { 2, slotSize.h - 18, 18, 18 };
20082 equippedIconFrame->addImage(equippedImgPos, 0xFFFFFFFF, "images/system/Equipped.png", "equipped icon img");
20083
20084 auto brokenIconFrame = slotFrame->addFrame("broken icon frame");
20085 brokenIconFrame->setSize(slotSize);
20086 brokenIconFrame->setHollow(true);
20087 brokenIconFrame->addImage(equippedImgPos, 0xFFFFFFFF, "images/system/Broken.png", "broken icon img");
20088}
20089
20090void resetInventorySlotFrames(const int player)
20091{
20092 //for ( int x = 0; x < players[player]->inventoryUI.getSizeX(); ++x )
20093 //{
20094 // for ( int y = Player::Inventory_t::PaperDollRows::DOLL_ROW_1; y < players[player]->inventoryUI.DEFAULT_INVENTORY_SIZEY + players[player]->inventoryUI.getPlayerBackpackBonusSizeY(); ++y )
20095 // {
20096 // if ( auto slotFrame = players[player]->inventoryUI.getInventorySlotFrame(x, y) )
20097 // {
20098 // slotFrame->setDisabled(true);
20099 // }
20100 // }
20101 //}
20102
20103 if ( players[player]->inventoryUI.frame )
20104 {
20105 for ( auto& pair : players[player]->inventoryUI.slotFrames )
20106 {
20107 if ( pair.second ) { pair.second->setDisabled(true); }
20108 }
20109 }
20110
20111 //for ( int x = 0; x < Player::Inventory_t::MAX_SPELLS_X; ++x )
20112 //{
20113 // for ( int y = 0; y < Player::Inventory_t::MAX_SPELLS_Y; ++y )
20114 // {
20115 // if ( auto slotFrame = players[player]->inventoryUI.getSpellSlotFrame(x, y) )
20116 // {
20117 // slotFrame->setDisabled(true);
20118 // }
20119 // }
20120 //}
20121
20122 if ( players[player]->inventoryUI.spellFrame )
20123 {
20124 for ( auto& pair : players[player]->inventoryUI.spellSlotFrames )
20125 {
20126 if ( pair.second ) { pair.second->setDisabled(true); }
20127 }
20128 }
20129
20130 //for ( int x = 0; x < Player::Inventory_t::MAX_CHEST_X; ++x )
20131 //{
20132 // for ( int y = 0; y < Player::Inventory_t::MAX_CHEST_Y; ++y )
20133 // {
20134 // if ( auto slotFrame = players[player]->inventoryUI.getChestSlotFrame(x, y) )
20135 // {
20136 // slotFrame->setDisabled(true);
20137 // }
20138 // }
20139 //}
20140
20141 if ( players[player]->inventoryUI.chestFrame )
20142 {
20143 for ( auto& pair : players[player]->inventoryUI.chestSlotFrames )
20144 {
20145 if ( pair.second ) { pair.second->setDisabled(true); }
20146 }
20147 }
20148
20149 //for ( int x = 0; x < Player::ShopGUI_t::MAX_SHOP_X; ++x )
20150 //{
20151 // for ( int y = 0; y < Player::ShopGUI_t::MAX_SHOP_Y; ++y )
20152 // {
20153 // if ( auto slotFrame = players[player]->shopGUI.getShopSlotFrame(x, y) )
20154 // {
20155 // slotFrame->setDisabled(true);
20156 // }
20157 // }
20158 //}
20159
20160 if ( players[player]->shopGUI.shopFrame )
20161 {
20162 for ( auto& pair : players[player]->shopGUI.shopSlotFrames )
20163 {
20164 if ( pair.second ) { pair.second->setDisabled(true); }
20165 }
20166 }
20167}
20168
20169bool getSlotFrameXYFromMousePos(const int player, int& outx, int& outy, bool spells)
20170{
20171 if ( !gui )
20172 {
20173 return false;
20174 }
20175
20176 if ( players[player]->inventoryUI.chestFrame && !spells
20177 && !players[player]->inventoryUI.chestFrame->isDisabled() )
20178 {
20179 for ( int x = 0; x < Player::Inventory_t::MAX_CHEST_X; ++x )
20180 {
20181 for ( int y = 0; y < Player::Inventory_t::MAX_CHEST_Y; ++y )
20182 {
20183 auto slotFrame = players[player]->inventoryUI.getChestSlotFrame(x, y);
20184 if ( !slotFrame )
20185 {
20186 continue;
20187 }
20188
20189 if ( !players[player]->inventoryUI.chestGUI.isSlotVisible(x, y) )
20190 {
20191 continue;
20192 }
20193
20194 if ( slotFrame->capturesMouseInRealtimeCoords() )
20195 {
20196 outx = x;
20197 outy = y;
20198 return true;
20199 }
20200 }
20201 }
20202 }
20203 if ( players[player]->inventoryUI.frame && !spells
20204 && players[player]->inventory_mode == INVENTORY_MODE_ITEM )
20205 {
20206 for ( int x = 0; x < players[player]->inventoryUI.getSizeX(); ++x )
20207 {
20208 for ( int y = Player::Inventory_t::DOLL_ROW_1; y < players[player]->inventoryUI.getSizeY(); ++y )
20209 {
20210 auto slotFrame = players[player]->inventoryUI.getInventorySlotFrame(x, y);
20211 if ( !slotFrame )
20212 {
20213 continue;
20214 }
20215
20216 if ( slotFrame->capturesMouseInRealtimeCoords() )
20217 {
20218 outx = x;
20219 outy = y;
20220 return true;
20221 }
20222 }
20223 }
20224 }
20225 if ( players[player]->inventoryUI.spellFrame && spells
20226 && players[player]->inventory_mode == INVENTORY_MODE_SPELL )
20227 {
20228 for ( int x = 0; x < Player::Inventory_t::MAX_SPELLS_X; ++x )
20229 {
20230 for ( int y = 0; y < Player::Inventory_t::MAX_SPELLS_Y; ++y )
20231 {
20232 auto slotFrame = players[player]->inventoryUI.getSpellSlotFrame(x, y);
20233 if ( !slotFrame )
20234 {
20235 continue;
20236 }
20237
20238 if ( !players[player]->inventoryUI.spellPanel.isSlotVisible(x, y) )
20239 {
20240 continue;
20241 }
20242
20243 if ( slotFrame->capturesMouseInRealtimeCoords() )
20244 {
20245 outx = x;
20246 outy = y;
20247 return true;
20248 }
20249 }
20250 }
20251 }
20252 return false;
20253}
20254
20255enum SlotFrameIndices : size_t {
20256 SLOTFRAME_BEATITUDE_FRAME = 0,
20257 SLOTFRAME_BEATITUDE_IMG = 0,
20258 SLOTFRAME_BROKEN_STATUS_FRAME = 1,
20259 SLOTFRAME_BROKEN_STATUS_IMG = 0,
20260 SLOTFRAME_ITEMSPRITE_FRAME = 2,
20261 SLOTFRAME_ITEMSPRITE_IMG = 0,
20262 SLOTFRAME_ITEMSPRITE_LABELBG_IMG = 1,
20263 SLOTFRAME_ITEMSPRITE_LABEL_IMG = 2,
20264 SLOTFRAME_UNUSABLE_ITEM_FRAME = 3,
20265 SLOTFRAME_UNUSABLE_IMG = 0,
20266 SLOTFRAME_APPRAISAL_FRAME = 4,
20267 SLOTFRAME_APPRAISAL_NOTIF_IMG = 0,
20268 SLOTFRAME_QTY_FRAME = 5,
20269 SLOTFRAME_QTY_TEXT = 0,
20270 SLOTFRAME_EQUIPPED_FRAME = 6,
20271 SLOTFRAME_EQUIPPED_IMG = 0,
20272 SLOTFRAME_BROKEN_ICON_FRAME = 7,
20273 SLOTFRAME_BROKEN_ICON_IMG = 0
20274};
20275void updateSlotFrameFromItem(Frame* slotFrame, void* itemPtr, bool forceUnusable)
20276{
20277 if ( !itemPtr || !slotFrame )
20278 {
20279 return;
20280 }
20281
20282 Item* item = (Item*)itemPtr;
20283
20284 int player = slotFrame->getOwner();
20285
20286 bool hiddenItemInGUI = false;
20287 if ( item->itemSpecialShopConsumable )
20288 {
20289 if ( stats[player]->getModifiedProficiency(PRO_TRADING) + statGetCHR(stats[player], players[player]->entity) < (((int)item->itemRequireTradingSkillInShop) * SHOP_CONSUMABLE_SKILL_REQ_PER_POINT) )
20290 {
20291 hiddenItemInGUI = true;
20292 }
20293 }
20294
20295 slotFrame->setDisabled(false);
20296
20297 auto& frames = slotFrame->getFrames();
20298
20299 auto spriteImageFrame = frames[SLOTFRAME_ITEMSPRITE_FRAME]; // slotFrame->findFrame("item sprite frame");
20300 auto spriteImage = spriteImageFrame->getImages()[SLOTFRAME_ITEMSPRITE_IMG]; // [spriteImageFrame->findImage("item sprite img");
20301
20302 if ( hiddenItemInGUI )
20303 {
20304 spriteImage->path = ("*#images/system/unknownitem.png");
20305 }
20306 else
20307 {
20308 spriteImage->path = getItemSpritePath(player, *item);
20309 }
20310 bool disableBackgrounds = false;
20311 if ( !strcmp(slotFrame->getName(), "dragging inventory item") ) // dragging item, no need for colors
20312 {
20313 disableBackgrounds = true;
20314 }
20315
20316 int* slotType = nullptr;
20317 if ( slotFrame->getUserData() )
20318 {
20319 slotType = (int*)slotFrame->getUserData();
20320 if ( *slotType == GAMEUI_FRAMEDATA_ANIMATING_ITEM
20321 || *slotType == GAMEUI_FRAMEDATA_ALCHEMY_RECIPE_SLOT )
20322 {
20323 disableBackgrounds = true;
20324 }
20325 }
20326
20327 bool isHotbarIcon = false;
20328 bool alchemyResultIcon = &GenericGUI[player].alchemyGUI.alchemyResultPotion == item;
20329 if ( spriteImage->path != "" )
20330 {
20331 spriteImageFrame->setDisabled(false);
20332 if ( inputs.getUIInteraction(player)->selectedItem == item )
20333 {
20334 if ( !strcmp(slotFrame->getName(), "hotbar slot item") ) // hotbar slots
20335 {
20336 // fade this icon
20337 spriteImage->color = makeColor( 255, 255, 255, 128);
20338 disableBackgrounds = true;
20339 isHotbarIcon = true;
20340 }
20341 }
20342 else if ( !strcmp(slotFrame->getName(), "hotbar slot item") ) // hotbar slots
20343 {
20344 isHotbarIcon = true;
20345 auto& hotbar_t = players[player]->hotbar;
20346 bool tryDimHotbarSlot = false;
20347 if ( hotbar_t.useHotbarFaceMenu && hotbar_t.faceMenuButtonHeld != Player::Hotbar_t::GROUP_NONE )
20348 {
20349 tryDimHotbarSlot = true;
20350 }
20351 spriteImage->color = 0xFFFFFFFF;
20352 if ( tryDimHotbarSlot )
20353 {
20354 std::string hotbarSlotParentStr = slotFrame->getParent()->getName();
20355 if ( hotbarSlotParentStr.find("hotbar slot ") != std::string::npos )
20356 {
20357 int num = stoi(hotbarSlotParentStr.substr(strlen("hotbar slot ")));
20358 if ( hotbar_t.faceMenuButtonHeld != hotbar_t.getFaceMenuGroupForSlot(num) )
20359 {
20360 // fade this icon
20361 spriteImage->color = makeColor( 255, 255, 255, 128);
20362 }
20363 }
20364 }
20365 }
20366 else
20367 {
20368 spriteImage->color = 0xFFFFFFFF;
20369 }
20370 if ( auto iconLabelImg = spriteImageFrame->getImages()[SLOTFRAME_ITEMSPRITE_LABEL_IMG]/*spriteImageFrame->findImage("icon label img")*/ )
20371 {
20372 iconLabelImg->path = ItemTooltips.getIconLabel(*item);
20373 iconLabelImg->disabled = true;
20374 const int size = 16;
20375 const int padx = spriteImageFrame->getSize().w / 2 - size / 2;
20376 iconLabelImg->pos = SDL_Rect{ spriteImageFrame->getSize().w - size - 1,
20377 1 /*spriteImageFrame->getSize().h - size*/, size, size };
20378 if ( iconLabelImg->path != "" )
20379 {
20380 iconLabelImg->disabled = (!item->identified || hiddenItemInGUI);
20381 }
20382 iconLabelImg->color = spriteImage->color;
20383 if ( auto iconLabelBgImg = spriteImageFrame->getImages()[SLOTFRAME_ITEMSPRITE_LABELBG_IMG]/*spriteImageFrame->findImage("icon label bg img")*/ )
20384 {
20385 iconLabelBgImg->pos.w = 24;
20386 iconLabelBgImg->pos.h = iconLabelBgImg->pos.w;
20387 iconLabelBgImg->pos.x = spriteImageFrame->getSize().w - iconLabelBgImg->pos.w - 1;
20388 iconLabelBgImg->pos.y = 1;
20389 iconLabelBgImg->disabled = iconLabelImg->disabled || disableBackgrounds;
20390 iconLabelBgImg->color = makeColor(255, 255, 255, 255);
20391 }
20392 }
20393 if ( slotFrame->getUserData() )
20394 {
20395 if ( *slotType == GAMEUI_FRAMEDATA_ALCHEMY_ITEM || *slotType == GAMEUI_FRAMEDATA_ALCHEMY_RECIPE_SLOT )
20396 {
20397 SDL_Color color;
20398 getColor(spriteImage->color, &color.r, &color.g, &color.b, &color.a);
20399 color.a /= 2;
20400 spriteImage->color = makeColor(color.r, color.g, color.b, color.a);
20401 }
20402 }
20403 }
20404
20405 if ( auto qtyFrame = frames[SLOTFRAME_QTY_FRAME]/*slotFrame->findFrame("quantity frame")*/ )
20406 {
20407 qtyFrame->setDisabled(true);
20408 bool drawQty = (item->count > 1) ? true : false;
20409 if ( !drawQty && GenericGUI[player].isNodeTinkeringCraftableItem(item->node) )
20410 {
20411 drawQty = true;
20412 }
20413 else if ( slotType && *slotType == GAMEUI_FRAMEDATA_ALCHEMY_RECIPE_ENTRY )
20414 {
20415 drawQty = true;
20416 }
20417 Uint32 qtyColor = 0xFFFFFFFF;
20418 bool stackable = false;
20419 Item*& selectedItem = inputs.getUIInteraction(player)->selectedItem;
20420 if ( selectedItem && !isHotbarIcon && !alchemyResultIcon
20421 && !(slotType
20422 && (*slotType == GAMEUI_FRAMEDATA_ANIMATING_ITEM
20423 || *slotType == GAMEUI_FRAMEDATA_ALCHEMY_RECIPE_SLOT
20424 || *slotType == GAMEUI_FRAMEDATA_ALCHEMY_RECIPE_ENTRY)) )
20425 {
20426 if ( item != selectedItem
20427 && !itemIsEquipped(selectedItem, player)
20428 && !itemIsEquipped(item, player) )
20429 {
20430 int selectedItemQty;
20431 int destItemQty;
20432 auto result = getItemStackingBehavior(player, selectedItem, item, selectedItemQty, destItemQty);
20433 if ( result.resultType == ITEM_ADDED_ENTIRELY_TO_DESTINATION_STACK
20434 || result.resultType == ITEM_ADDED_PARTIALLY_TO_DESTINATION_STACK )
20435 {
20436 drawQty = true;
20437 qtyColor = hudColors.characterSheetGreen;
20438 stackable = true;
20439 }
20440 }
20441 }
20442 if ( drawQty )
20443 {
20444 qtyFrame->setDisabled(false);
20445 if ( auto qtyText = qtyFrame->getFields()[SLOTFRAME_QTY_TEXT]/*qtyFrame->findField("quantity text")*/ )
20446 {
20447 char qtybuf[32] = "";
20448 if ( stackable )
20449 {
20450 if ( item->count == 1 )
20451 {
20452 snprintf(qtybuf, sizeof(qtybuf), "+");
20453 }
20454 else if ( item->count > 1 )
20455 {
20456 snprintf(qtybuf, sizeof(qtybuf), "%d+", item->count);
20457 }
20458 }
20459 else
20460 {
20461 snprintf(qtybuf, sizeof(qtybuf), "%d", item->count);
20462 }
20463 if ( strcmp(qtyText->getText(), qtybuf) )
20464 {
20465 qtyText->setText(qtybuf);
20466 }
20467 qtyText->setColor(qtyColor);
20468 }
20469 }
20470 }
20471
20472 if ( auto beatitudeFrame = frames[SLOTFRAME_BEATITUDE_FRAME]/*slotFrame->findFrame("beatitude status frame")*/ )
20473 {
20474 beatitudeFrame->setDisabled(true);
20475 //spriteImage->outline = false;
20476 if ( !disableBackgrounds )
20477 {
20478 if ( auto beatitudeImg = beatitudeFrame->getImages()[SLOTFRAME_BEATITUDE_IMG]/*beatitudeFrame->findImage("beatitude status bg")*/ )
20479 {
20480 if ( !item->identified )
20481 {
20482 //beatitudeImg->color = makeColor( 128, 128, 0, 125);
20483 if ( !(slotFrame->getUserData() && *slotType == GAMEUI_FRAMEDATA_SHOP_ITEM) )
20484 {
20485 beatitudeFrame->setDisabled(false);
20486 }
20487 //spriteImage->outlineColor = makeColor(210, 183, 76, 255);
20488 //spriteImage->outline = true;
20489 }
20490 else if ( item->beatitude < 0 )
20491 {
20492 //beatitudeImg->color = makeColor( 128, 0, 0, 125);
20493 beatitudeFrame->setDisabled(false);
20494 //spriteImage->outlineColor = hudColors.characterSheetRed;
20495 //spriteImage->outline = true;
20496 }
20497 else if ( item->beatitude > 0 )
20498 {
20499 /*if ( colorblind )
20500 {
20501 beatitudeImg->color = makeColor( 100, 245, 255, 65);
20502 }
20503 else
20504 {
20505 beatitudeImg->color = makeColor( 0, 255, 0, 65);
20506 }*/
20507 //spriteImage->outlineColor = hudColors.characterSheetHeadingText;
20508 //spriteImage->outline = true;
20509 beatitudeFrame->setDisabled(false);
20510 }
20511 /*if ( !spriteImage->outline )
20512 {
20513 spriteImage->outlineColor = 0;
20514 }
20515 else
20516 {
20517 spriteImage->outlineColor = makeColor(0, 0, 0, 255);
20518 }*/
20519 if ( !beatitudeFrame->isDisabled() )
20520 {
20521 //beatitudeImg->color = uint32ColorWhite;
20522 if ( isHotbarIcon || (slotFrame->getUserData() && *slotType == GAMEUI_FRAMEDATA_WORLDTOOLTIP_ITEM) )
20523 {
20524 if ( !item->identified )
20525 {
20526 static const char* unidentifyHotbarPath = "*#images/ui/HUD/hotbar/HUD_Quickbar_Slot_Box_Overlay_App01.png";
20527 if ( strcmp(unidentifyHotbarPath, beatitudeImg->path.c_str()) )
20528 {
20529 beatitudeImg->path = unidentifyHotbarPath;
20530 }
20531 }
20532 else if ( item->beatitude > 0 )
20533 {
20534 static const char* blessHotbarPath = "*#images/ui/HUD/hotbar/HUD_Quickbar_Slot_Box_Overlay_Bless01.png";
20535 if ( strcmp(blessHotbarPath, beatitudeImg->path.c_str()) )
20536 {
20537 beatitudeImg->path = blessHotbarPath;
20538 }
20539 }
20540 else if ( item->beatitude < 0 )
20541 {
20542 static const char* curseHotbarPath = "*#images/ui/HUD/hotbar/HUD_Quickbar_Slot_Box_Overlay_Curse01.png";
20543 if ( strcmp(curseHotbarPath, beatitudeImg->path.c_str()) )
20544 {
20545 beatitudeImg->path = curseHotbarPath;
20546 }
20547 }
20548 }
20549 else
20550 {
20551 if ( !item->identified )
20552 {
20553 static const char* unidentifyPath = "*#images/ui/Inventory/HUD_Inventory_Item_App00B.png";
20554 if ( strcmp(unidentifyPath, beatitudeImg->path.c_str()) )
20555 {
20556 beatitudeImg->path = unidentifyPath;
20557 }
20558 }
20559 else if ( item->beatitude > 0 )
20560 {
20561 static const char* blessPath = "*#images/ui/Inventory/HUD_Inventory_Item_Bless00B.png";
20562 if ( strcmp(blessPath, beatitudeImg->path.c_str()) )
20563 {
20564 beatitudeImg->path = blessPath;
20565 }
20566 }
20567 else if ( item->beatitude < 0 )
20568 {
20569 static const char* cursePath = "*#images/ui/Inventory/HUD_Inventory_Item_Curse00B.png";
20570 if ( strcmp(cursePath, beatitudeImg->path.c_str()) )
20571 {
20572 beatitudeImg->path = cursePath;
20573 }
20574 }
20575 }
20576 }
20577 }
20578 }
20579 }
20580
20581 if ( auto brokenStatusFrame = frames[SLOTFRAME_BROKEN_STATUS_FRAME]/*slotFrame->findFrame("broken status frame")*/ )
20582 {
20583 brokenStatusFrame->setDisabled(true);
20584 if ( !disableBackgrounds )
20585 {
20586 if ( item->status == BROKEN )
20587 {
20588 if ( players[player]->shopGUI.bOpen
20589 && isItemSellableToShop(player, item)
20590 && !(slotFrame->getUserData() && *slotType == GAMEUI_FRAMEDATA_SHOP_ITEM) )
20591 {
20592 // don't grey out this item
20593 }
20594 else
20595 {
20596 brokenStatusFrame->setDisabled(false);
20597 auto brokenStatusImg = brokenStatusFrame->getImages()[SLOTFRAME_BROKEN_STATUS_IMG];/*brokenStatusFrame->findImage("broken status bg");*/
20598 if ( isHotbarIcon || (slotFrame->getUserData() && *slotType == GAMEUI_FRAMEDATA_WORLDTOOLTIP_ITEM) )
20599 {
20600 brokenStatusImg->path = "*#images/ui/HUD/hotbar/HUD_Quickbar_Slot_Box_Overlay_01.png";
20601 }
20602 else
20603 {
20604 brokenStatusImg->path = "images/system/white.png";
20605 }
20606 }
20607 }
20608 }
20609 }
20610
20611 if ( auto unusableFrame = frames[SLOTFRAME_UNUSABLE_ITEM_FRAME]/*slotFrame->findFrame("unusable item frame")*/ )
20612 {
20613 bool greyedOut = forceUnusable || hiddenItemInGUI;
20614 unusableFrame->setDisabled(true);
20615
20616 if ( (slotFrame->getUserData() && *slotType == GAMEUI_FRAMEDATA_WORLDTOOLTIP_ITEM) )
20617 {
20618 // no grey out
20619 }
20620 else if ( !disableBackgrounds )
20621 {
20622 if ( players[player] && players[player]->entity && players[player]->entity->effectShapeshift != NOTHING )
20623 {
20624 // shape shifted, disable some items
20625 if ( !item->usableWhileShapeshifted(stats[player]) )
20626 {
20627 greyedOut = true;
20628 }
20629 }
20630 if ( !greyedOut && client_classes[player] == CLASS_SHAMAN
20631 && item->type == SPELL_ITEM && !(playerUnlockedShamanSpell(player, item)) )
20632 {
20633 greyedOut = true;
20634 }
20635
20636 if ( greyedOut )
20637 {
20638 unusableFrame->setDisabled(false);
20639 }
20640 }
20641 }
20642
20643 bool equipped = false;
20644 bool broken = false;
20645 if ( itemCategory(item) != SPELL_CAT )
20646 {
20647 if ( itemIsEquipped(item, player) )
20648 {
20649 equipped = true;
20650 }
20651 else if ( item->status == BROKEN )
20652 {
20653 broken = true;
20654 }
20655 else if ( alchemyResultIcon && stats[player]->weapon
20656 && itemCategory(stats[player]->weapon) == POTION
20657 && stats[player]->weapon->identified && item->identified )
20658 {
20659 int selectedItemQty;
20660 int destItemQty;
20661 auto result = getItemStackingBehavior(player, item, stats[player]->weapon, selectedItemQty, destItemQty);
20662 if ( result.resultType == ITEM_ADDED_ENTIRELY_TO_DESTINATION_STACK
20663 || result.resultType == ITEM_ADDED_PARTIALLY_TO_DESTINATION_STACK )
20664 {
20665 equipped = true;
20666 }
20667 }
20668 }
20669 else
20670 {
20671 spell_t* spell = getSpellFromItem(player, item);
20672 if ( players[player]->magic.selectedSpell() == spell
20673 && (players[player]->magic.selected_spell_last_appearance == item->appearance || players[player]->magic.selected_spell_last_appearance == -1) )
20674 {
20675 equipped = true;
20676 }
20677 }
20678
20679 if ( auto equippedIconFrame = frames[SLOTFRAME_EQUIPPED_FRAME]/*slotFrame->findFrame("equipped icon frame")*/ )
20680 {
20681 equippedIconFrame->setDisabled(true);
20682 if ( equipped && (!disableBackgrounds || (slotType && (*slotType == GAMEUI_FRAMEDATA_ANIMATING_ITEM))) )
20683 {
20684 equippedIconFrame->setDisabled(false);
20685 }
20686 }
20687 if ( auto brokenIconFrame = frames[SLOTFRAME_BROKEN_ICON_FRAME]/*slotFrame->findFrame("broken icon frame")*/ )
20688 {
20689 brokenIconFrame->setDisabled(true);
20690 if ( broken && (!disableBackgrounds || (slotType && (*slotType == GAMEUI_FRAMEDATA_ALCHEMY_RECIPE_SLOT))) )
20691 {
20692 brokenIconFrame->setDisabled(false);
20693 }
20694 }
20695
20696 if ( auto appraisalFrame = frames[SLOTFRAME_APPRAISAL_FRAME]/*slotFrame->findFrame("appraisal frame")*/ )
20697 {
20698 appraisalFrame->setDisabled(true);
20699 appraisalFrame->setDrawCallback(nullptr);
20700 if ( !item->identified )
20701 {
20702 appraisalFrame->setDisabled(false);
20703 }
20704 else if ( item->notifyIcon )
20705 {
20706 appraisalFrame->setDisabled(false);
20707 }
20708 if ( !disableBackgrounds && players[player]->inventoryUI.appraisal.current_item > 0
20709 && slotFrame->getOpacity() > 0.999
20710 && !item->identified && item->node && item->node->list == &stats[player]->inventory
20711 && item->uid == players[player]->inventoryUI.appraisal.current_item )
20712 {
20713 if ( isHotbarIcon )
20714 {
20715 appraisalFrame->setDisabled(false);
20716 appraisalFrame->setDrawCallback([](const Widget& widget, SDL_Rect rect) {
20717 drawUnidentifiedItemEffectHotbarCallback(widget, rect);
20718 });
20719 }
20720 else
20721 {
20722 appraisalFrame->setDisabled(false);
20723 appraisalFrame->setDrawCallback([](const Widget& widget, SDL_Rect rect) {
20724 drawUnidentifiedItemEffectCallback(widget, rect);
20725 });
20726 }
20727 }
20728 if ( !appraisalFrame->isDisabled() )
20729 {
20730 auto img = appraisalFrame->getImages()[SLOTFRAME_APPRAISAL_NOTIF_IMG];/*appraisalFrame->findImage("new notif img");*/
20731 img->disabled = true;
20732 if ( !item->identified )
20733 {
20734 img->disabled = false;
20735 img->path = "images/ui/Inventory/tooltips/Unidentified_Icon.png";
20736 img->pos.x = 28;
20737 img->pos.y = 4;
20738 img->pos.w = 10;
20739 img->pos.h = 14;
20740 if ( isHotbarIcon || (slotFrame->getUserData() && *slotType == GAMEUI_FRAMEDATA_WORLDTOOLTIP_ITEM) )
20741 {
20742 img->pos.x = 30;
20743 img->pos.y = 6;
20744 }
20745 }
20746 else if ( item->notifyIcon )
20747 {
20748 img->pos.x = 4;
20749 img->pos.y = 4;
20750 img->pos.w = 6;
20751 img->pos.h = 14;
20752 if ( isHotbarIcon && !players[player]->hotbar.useHotbarFaceMenu )
20753 {
20754 img->pos.x = 10;
20755 img->pos.y = 2;
20756
20757 // to push to right of "10" text if we label that
20758 /*if ( players[player]->hotbar.slots()[NUM_HOTBAR_SLOTS - 1].item == item->uid )
20759 {
20760 if ( slotFrame && slotFrame->getParent() && !strcmp(slotFrame->getParent()->getName(), "hotbar slot 9") )
20761 {
20762 img->pos.x += 6;
20763 }
20764 }*/
20765 }
20766 img->disabled = false;
20767 switch ( players[player]->inventoryUI.appraisal.itemNotifyAnimState )
20768 {
20769 case 0:
20770 img->path = "images/ui/Inventory/tooltips/ExclamationAnim00.png";
20771 break;
20772 case 1:
20773 img->path = "images/ui/Inventory/tooltips/ExclamationAnim01.png";
20774 break;
20775 case 2:
20776 img->path = "images/ui/Inventory/tooltips/ExclamationAnim02.png";
20777 break;
20778 default:
20779 img->path = "images/ui/Inventory/tooltips/ExclamationAnim00.png";
20780 break;
20781 }
20782 }
20783 }
20784 }
20785}
20786
20787void createInventoryTooltipFrame(const int player)
20788{
20789 if ( !gui )
20790 {
20791 return;
20792 }
20793
20794 //const std::string headerFont = "fonts/pixelmix.ttf#14#2";
20795 //const std::string bodyFont = "fonts/pixelmix.ttf#12#2";
20796 const std::string headerFont = "fonts/pixel_maz_multiline.ttf#16#2";
20797 const std::string bodyFont = "fonts/pixel_maz_multiline.ttf#16#2";
20798
20799 if ( !players[player]->inventoryUI.tooltipContainerFrame )
20800 {
20801 char name[32];
20802 snprintf(name, sizeof(name), "player tooltip container %d", player);
20803 players[player]->inventoryUI.tooltipContainerFrame = gameUIFrame[player]->addFrame(name);
20804 players[player]->inventoryUI.tooltipContainerFrame->setSize(
20805 SDL_Rect{ players[player]->camera_virtualx1(),
20806 players[player]->camera_virtualy1(),
20807 players[player]->camera_virtualWidth(),
20808 players[player]->camera_virtualHeight() });
20809 players[player]->inventoryUI.tooltipContainerFrame->setHollow(true);
20810 players[player]->inventoryUI.tooltipContainerFrame->setDisabled(false);
20811 players[player]->inventoryUI.tooltipContainerFrame->setInheritParentFrameOpacity(false);
20812 }
20813 if ( !players[player]->inventoryUI.titleOnlyTooltipFrame )
20814 {
20815 char name[32];
20816 snprintf(name, sizeof(name), "player title only tooltip %d", player);
20817 players[player]->inventoryUI.titleOnlyTooltipFrame = players[player]->inventoryUI.tooltipContainerFrame->addFrame(name);
20818 auto tooltipFrame = players[player]->inventoryUI.titleOnlyTooltipFrame;
20819 tooltipFrame->setSize(SDL_Rect{ 0, 0, 0, 0 });
20820 tooltipFrame->setHollow(true);
20821 tooltipFrame->setDisabled(true);
20822 tooltipFrame->setInheritParentFrameOpacity(false);
20823
20824 auto tooltipTextField = tooltipFrame->addField("title only header", 1024);
20825 tooltipTextField->setText("Nothing");
20826 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
20827 tooltipTextField->setFont(headerFont.c_str());
20828 tooltipTextField->setHJustify(Field::justify_t::CENTER);
20829 tooltipTextField->setVJustify(Field::justify_t::TOP);
20830 tooltipTextField->setTextColor(hudColors.characterSheetGreen);
20831 tooltipTextField->setPaddingPerLine(-2);
20832
20833 Uint32 color = makeColor(255, 255, 255, 255);
20834 tooltipFrame->addImage(SDL_Rect{ 0, 0, tooltipFrame->getSize().w, 28 },
20835 color, "*#images/ui/Inventory/tooltips/Hover_T00_TitleOnly.png", "tooltip top background");
20836 tooltipFrame->addImage(SDL_Rect{ 0, 0, 16, 28 },
20837 color, "*#images/ui/Inventory/tooltips/Hover_TL00_TitleOnly.png", "tooltip top left");
20838 tooltipFrame->addImage(SDL_Rect{ 0, 0, 16, 28 },
20839 color, "*#images/ui/Inventory/tooltips/Hover_TR00_TitleOnly.png", "tooltip top right");
20840 }
20841 if ( !players[player]->inventoryUI.tooltipFrame )
20842 {
20843 char name[32];
20844 snprintf(name, sizeof(name), "player tooltip %d", player);
20845 players[player]->inventoryUI.tooltipFrame = players[player]->inventoryUI.tooltipContainerFrame->addFrame(name);
20846 auto tooltipFrame = players[player]->inventoryUI.tooltipFrame;
20847 tooltipFrame->setSize(SDL_Rect{ 0, 0, 0, 0 });
20848 tooltipFrame->setHollow(true);
20849 tooltipFrame->setDisabled(true);
20850 tooltipFrame->setInheritParentFrameOpacity(false);
20851 }
20852 else
20853 {
20854 return;
20855 }
20856
20857 auto tooltipFrame = players[player]->inventoryUI.tooltipFrame;
20858
20859 Uint32 color = makeColor( 255, 255, 255, 255);
20860 tooltipFrame->addImage(SDL_Rect{ 0, 0, tooltipFrame->getSize().w, 28 },
20861 color, "*#images/ui/Inventory/tooltips/Hover_T00.png", "tooltip top background");
20862 tooltipFrame->addImage(SDL_Rect{ 0, 0, 16, 28 },
20863 color, "*#images/ui/Inventory/tooltips/Hover_TL00.png", "tooltip top left");
20864 tooltipFrame->addImage(SDL_Rect{ 0, 0, 16, 28 },
20865 color, "*#images/ui/Inventory/tooltips/Hover_TR00.png", "tooltip top right");
20866
20867 tooltipFrame->addImage(SDL_Rect{ 0, 0, tooltipFrame->getSize().w, 52 },
20868 color, "*#images/ui/Inventory/tooltips/Hover_C00.png", "tooltip middle background");
20869 tooltipFrame->addImage(SDL_Rect{ 0, 0, 16, 52 },
20870 color, "*#images/ui/Inventory/tooltips/Hover_L00.png", "tooltip middle left");
20871 tooltipFrame->addImage(SDL_Rect{ 0, 0, 16, 52 },
20872 color, "*#images/ui/Inventory/tooltips/Hover_R00.png", "tooltip middle right");
20873
20874 tooltipFrame->addImage(SDL_Rect{ 0, 0, tooltipFrame->getSize().w, 26 },
20875 color, "*#images/ui/Inventory/tooltips/Hover_B00.png", "tooltip bottom background");
20876 tooltipFrame->addImage(SDL_Rect{ 0, 0, 16, 26 },
20877 color, "*#images/ui/Inventory/tooltips/Hover_BL01.png", "tooltip bottom left");
20878 tooltipFrame->addImage(SDL_Rect{ 0, 0, 16, 26 },
20879 color, "*#images/ui/Inventory/tooltips/Hover_BR01.png", "tooltip bottom right");
20880
20881 auto tooltipTextField = tooltipFrame->addField("inventory mouse tooltip header", 1024);
20882 tooltipTextField->setText("Nothing");
20883 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
20884 tooltipTextField->setFont(headerFont.c_str());
20885 tooltipTextField->setHJustify(Field::justify_t::LEFT);
20886 tooltipTextField->setVJustify(Field::justify_t::CENTER);
20887 tooltipTextField->setColor(makeColor( 67, 195, 157, 255));
20888
20889 // temporary debug stuff
20890 {
20891 Frame::image_t* tmp = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
20892 0xFFFFFFFF, "images/system/white.png", "inventory mouse tooltip min");
20893 tmp->color = makeColor( 255, 0, 0, 255);
20894 tmp->disabled = true;
20895 tmp = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
20896 0xFFFFFFFF, "images/system/white.png", "inventory mouse tooltip max");
20897 tmp->color = makeColor( 0, 255, 0, 255);
20898 tmp->disabled = true;
20899 tmp = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
20900 0xFFFFFFFF, "images/system/white.png", "inventory mouse tooltip header max");
20901 tmp->color = makeColor( 0, 255, 255, 255);
20902 tmp->disabled = true;
20903 tmp = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
20904 0xFFFFFFFF, "images/system/white.png", "inventory mouse tooltip header bg");
20905 tmp->color = makeColor( 255, 255, 255, 255);
20906 tmp->disabled = true;
20907 tmp = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
20908 0xFFFFFFFF, "images/system/white.png", "inventory mouse tooltip header bg new");
20909 tmp->color = makeColor( 255, 255, 0, 255);
20910 tmp->disabled = true;
20911
20912 tmp = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
20913 makeColor(255, 255, 255, 0), "images/ui/Inventory/tooltips/TooltipGradientTop.png", "inventory mouse tooltip fade top");
20914 tmp->disabled = true;
20915 tmp->ontop = true;
20916 tmp = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
20917 makeColor(255, 255, 255, 0), "images/ui/Inventory/tooltips/TooltipGradientBottom.png", "inventory mouse tooltip fade bottom");
20918 tmp->disabled = true;
20919 tmp->ontop = true;
20920 }
20921
20922 if ( auto attrFrame = tooltipFrame->addFrame("inventory mouse tooltip attributes frame") )
20923 {
20924 attrFrame->setHollow(true);
20925 attrFrame->setSize(SDL_Rect{ 0, 0, 0, 0 });
20926
20927 auto spellImageBg = attrFrame->addImage(SDL_Rect{ 0, 0, 48, 48 },
20928 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/SpellBorder_00.png", "inventory mouse tooltip spell image bg");
20929 spellImageBg->disabled = true;
20930 //spellImageBg->color = makeColor( 125, 125, 125, 228);
20931 auto spellImage = attrFrame->addImage(SDL_Rect{ 0, 0, 36, 36 },
20932 0xFFFFFFFF, "images/system/white.png", "inventory mouse tooltip spell image");
20933 spellImage->disabled = true;
20934
20935 attrFrame->addImage(SDL_Rect{ 0, 0, 24, 24 },
20936 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/HUD_Tooltip_Icon_Damage_00.png", "inventory mouse tooltip primary image");
20937 tooltipTextField = attrFrame->addField("inventory mouse tooltip primary value", 256);
20938 tooltipTextField->setText("Nothing");
20939 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
20940 tooltipTextField->setFont(bodyFont.c_str());
20941 tooltipTextField->setHJustify(Field::justify_t::LEFT);
20942 tooltipTextField->setVJustify(Field::justify_t::CENTER);
20943 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
20944
20945 tooltipTextField = attrFrame->addField("inventory mouse tooltip primary value highlight", 256);
20946 tooltipTextField->setText("Nothing");
20947 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
20948 tooltipTextField->setFont(bodyFont.c_str());
20949 tooltipTextField->setHJustify(Field::justify_t::LEFT);
20950 tooltipTextField->setVJustify(Field::justify_t::CENTER);
20951 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
20952
20953 tooltipTextField = attrFrame->addField("inventory mouse tooltip primary value positive text", 256);
20954 tooltipTextField->setText("Nothing");
20955 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
20956 tooltipTextField->setFont(bodyFont.c_str());
20957 tooltipTextField->setHJustify(Field::justify_t::LEFT);
20958 tooltipTextField->setVJustify(Field::justify_t::CENTER);
20959 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
20960
20961 tooltipTextField = attrFrame->addField("inventory mouse tooltip primary value negative text", 256);
20962 tooltipTextField->setText("Nothing");
20963 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
20964 tooltipTextField->setFont(bodyFont.c_str());
20965 tooltipTextField->setHJustify(Field::justify_t::LEFT);
20966 tooltipTextField->setVJustify(Field::justify_t::CENTER);
20967 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
20968
20969 tooltipTextField = attrFrame->addField("inventory mouse tooltip primary value slot name", 256);
20970 tooltipTextField->setText("Nothing");
20971 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
20972 tooltipTextField->setFont(bodyFont.c_str());
20973 tooltipTextField->setHJustify(Field::justify_t::RIGHT);
20974 tooltipTextField->setVJustify(Field::justify_t::CENTER);
20975 tooltipTextField->setColor(0xFFFFFFFF);
20976
20977 attrFrame->addImage(SDL_Rect{ 0, 0, 24, 24 },
20978 0xFFFFFFFF, "images/system/con32.png", "inventory mouse tooltip secondary image");
20979 tooltipTextField = attrFrame->addField("inventory mouse tooltip secondary value", 256);
20980 tooltipTextField->setText("Nothing");
20981 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
20982 tooltipTextField->setFont(bodyFont.c_str());
20983 tooltipTextField->setHJustify(Field::justify_t::LEFT);
20984 tooltipTextField->setVJustify(Field::justify_t::CENTER);
20985 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
20986
20987 tooltipTextField = attrFrame->addField("inventory mouse tooltip secondary value highlight", 256);
20988 tooltipTextField->setText("Nothing");
20989 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
20990 tooltipTextField->setFont(bodyFont.c_str());
20991 tooltipTextField->setHJustify(Field::justify_t::LEFT);
20992 tooltipTextField->setVJustify(Field::justify_t::CENTER);
20993 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
20994
20995 tooltipTextField = attrFrame->addField("inventory mouse tooltip secondary value positive text", 256);
20996 tooltipTextField->setText("Nothing");
20997 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
20998 tooltipTextField->setFont(bodyFont.c_str());
20999 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21000 tooltipTextField->setVJustify(Field::justify_t::CENTER);
21001 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
21002
21003 tooltipTextField = attrFrame->addField("inventory mouse tooltip secondary value negative text", 256);
21004 tooltipTextField->setText("Nothing");
21005 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21006 tooltipTextField->setFont(bodyFont.c_str());
21007 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21008 tooltipTextField->setVJustify(Field::justify_t::CENTER);
21009 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
21010
21011 attrFrame->addImage(SDL_Rect{ 0, 0, 24, 24 },
21012 0xFFFFFFFF, "images/system/con32.png", "inventory mouse tooltip third image");
21013 tooltipTextField = attrFrame->addField("inventory mouse tooltip third value", 256);
21014 tooltipTextField->setText("Nothing");
21015 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21016 tooltipTextField->setFont(bodyFont.c_str());
21017 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21018 tooltipTextField->setVJustify(Field::justify_t::CENTER);
21019 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
21020
21021 tooltipTextField = attrFrame->addField("inventory mouse tooltip third value highlight", 256);
21022 tooltipTextField->setText("Nothing");
21023 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21024 tooltipTextField->setFont(bodyFont.c_str());
21025 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21026 tooltipTextField->setVJustify(Field::justify_t::CENTER);
21027 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
21028
21029 tooltipTextField = attrFrame->addField("inventory mouse tooltip third value positive text", 256);
21030 tooltipTextField->setText("Nothing");
21031 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21032 tooltipTextField->setFont(bodyFont.c_str());
21033 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21034 tooltipTextField->setVJustify(Field::justify_t::CENTER);
21035 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
21036
21037 tooltipTextField = attrFrame->addField("inventory mouse tooltip third value negative text", 256);
21038 tooltipTextField->setText("Nothing");
21039 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21040 tooltipTextField->setFont(bodyFont.c_str());
21041 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21042 tooltipTextField->setVJustify(Field::justify_t::CENTER);
21043 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
21044
21045 tooltipTextField = attrFrame->addField("inventory mouse tooltip attributes text", 1024);
21046 tooltipTextField->setText("Nothing");
21047 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21048 tooltipTextField->setFont(bodyFont.c_str());
21049 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21050 tooltipTextField->setVJustify(Field::justify_t::TOP);
21051 tooltipTextField->setColor(makeColor( 188, 154, 114, 255));
21052 }
21053 if ( auto descFrame = tooltipFrame->addFrame("inventory mouse tooltip description frame") )
21054 {
21055 descFrame->setHollow(true);
21056 descFrame->setSize(SDL_Rect{ 0, 0, 0, 0 });
21057
21058 descFrame->addImage(SDL_Rect{ 0, 0, 0, 1 },
21059 makeColor( 49, 53, 61, 255),
21060 "images/system/white.png", "inventory mouse tooltip description divider");
21061
21062 tooltipTextField = descFrame->addField("inventory mouse tooltip description", 1024);
21063 tooltipTextField->setText("Nothing");
21064 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21065 tooltipTextField->setFont(bodyFont.c_str());
21066 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21067 tooltipTextField->setVJustify(Field::justify_t::TOP);
21068 //tooltipTextField->setColor(makeColor( 188, 154, 114, 255));
21069 //tooltipTextField->setColor(0xFFFFFFFF);
21070 tooltipTextField->setTextColor(makeColor( 67, 195, 157, 255));
21071
21072 tooltipTextField = descFrame->addField("inventory mouse tooltip description positive text", 1024);
21073 tooltipTextField->setText("Nothing");
21074 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21075 tooltipTextField->setFont(bodyFont.c_str());
21076 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21077 tooltipTextField->setVJustify(Field::justify_t::TOP);
21078 //tooltipTextField->setColor(makeColor( 1, 151, 246, 255));
21079 tooltipTextField->setColor(0xFFFFFFFF);
21080 tooltipTextField->setTextColor(makeColor( 188, 154, 114, 255));
21081
21082 tooltipTextField = descFrame->addField("inventory mouse tooltip description negative text", 1024);
21083 tooltipTextField->setText("Nothing");
21084 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21085 tooltipTextField->setFont(bodyFont.c_str());
21086 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21087 tooltipTextField->setVJustify(Field::justify_t::TOP);
21088 tooltipTextField->setColor(0xFFFFFFFF);
21089 tooltipTextField->setTextColor(makeColor( 215, 38, 61, 255));
21090 }
21091 if ( auto valueFrame = tooltipFrame->addFrame("inventory mouse tooltip value frame") )
21092 {
21093 valueFrame->setHollow(true);
21094 valueFrame->setSize(SDL_Rect{ 0, 0, 0, 0 });
21095
21096 valueFrame->addImage(SDL_Rect{ 0, 0, 0, 0 },
21097 makeColor( 49, 53, 61, 255),
21098 "images/system/white.png", "inventory mouse tooltip value background");
21099
21100 valueFrame->addImage(SDL_Rect{ 0, 0, 0, 1 },
21101 makeColor( 49, 53, 61, 255),
21102 "images/system/white.png", "inventory mouse tooltip value divider");
21103
21104 tooltipTextField = valueFrame->addField("inventory mouse tooltip identified value", 64);
21105 tooltipTextField->setText("Nothing");
21106 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21107 tooltipTextField->setFont(bodyFont.c_str());
21108 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21109 tooltipTextField->setVJustify(Field::justify_t::CENTER);
21110 tooltipTextField->setColor(makeColor( 188, 154, 114, 255));
21111
21112 valueFrame->addImage(SDL_Rect{ 0, 0, 16, 16 },
21113 0xFFFFFFFF,
21114 "*#images/ui/Inventory/tooltips/HUD_Tooltip_Icon_Money_00.png",
21115 "inventory mouse tooltip gold image");
21116
21117 tooltipTextField = valueFrame->addField("inventory mouse tooltip gold value", 64);
21118 tooltipTextField->setText("Nothing");
21119 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21120 tooltipTextField->setFont(bodyFont.c_str());
21121 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21122 tooltipTextField->setVJustify(Field::justify_t::CENTER);
21123 tooltipTextField->setColor(makeColor( 188, 154, 114, 255));
21124
21125 valueFrame->addImage(SDL_Rect{ 0, 0, 16, 16 },
21126 0xFFFFFFFF,
21127 "*#images/ui/Inventory/tooltips/HUD_Tooltip_Icon_WGT_00.png",
21128 "inventory mouse tooltip weight image");
21129
21130 tooltipTextField = valueFrame->addField("inventory mouse tooltip weight value", 64);
21131 tooltipTextField->setText("Nothing");
21132 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21133 tooltipTextField->setFont(bodyFont.c_str());
21134 tooltipTextField->setHJustify(Field::justify_t::LEFT);
21135 tooltipTextField->setVJustify(Field::justify_t::CENTER);
21136 tooltipTextField->setColor(makeColor( 188, 154, 114, 255));
21137 }
21138 if ( auto promptFrame = tooltipFrame->addFrame("inventory mouse tooltip prompt frame") )
21139 {
21140 promptFrame->setHollow(true);
21141 promptFrame->setSize(SDL_Rect{ 0, 0, 0, 0 });
21142
21143 tooltipTextField = promptFrame->addField("inventory mouse tooltip prompt", 1024);
21144 tooltipTextField->setText("Nothing");
21145 tooltipTextField->setSize(SDL_Rect{ 0, 0, 0, 0 });
21146 tooltipTextField->setFont(bodyFont.c_str());
21147 tooltipTextField->setHJustify(Field::justify_t::RIGHT);
21148 tooltipTextField->setVJustify(Field::justify_t::TOP);
21149 tooltipTextField->setColor(makeColor( 148, 82, 3, 255));
21150
21151 }
21152 auto tooltipPromptImg = tooltipFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "inventory mouse tooltip prompt img");
21153 tooltipPromptImg->disabled = true;
21154
21155 char name[32];
21156 snprintf(name, sizeof(name), "player interact %d", player);
21157 if ( auto interactFrame = gameUIFrame[player]->addFrame(name) )
21158 {
21159 players[player]->inventoryUI.interactFrame = interactFrame;
21160 const int interactWidth = 106;
21161 interactFrame->setSize(SDL_Rect{ 0, 0, interactWidth + 6 * 2, 100 });
21162 interactFrame->setDisabled(true);
21163 interactFrame->setInheritParentFrameOpacity(false);
21164
21165 Uint32 color = makeColor( 255, 255, 255, 255);
21166 const int topBackgroundHeight = 30;
21167 const int optionHeight = 20;
21168
21169 interactFrame->addImage(SDL_Rect{ 24, 0, 0, 30 },
21170 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_T03.png", "interact top background");
21171 interactFrame->addImage(SDL_Rect{ 0, 0, 24, 30 },
21172 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_TL03.png", "interact top left");
21173 interactFrame->addImage(SDL_Rect{ 0, 0, 24, 30 },
21174 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_TR03.png", "interact top right");
21175
21176 interactFrame->addImage(SDL_Rect{ 24, 30, 0, 12 },
21177 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_C03.png", "interact middle background");
21178 auto ml = interactFrame->addImage(SDL_Rect{ 0, 30, 24, 12 },
21179 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_L03.png", "interact middle left");
21180 ml->tiled = true;
21181 auto mr = interactFrame->addImage(SDL_Rect{ 0, 30, 24, 12 },
21182 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_R03.png", "interact middle right");
21183 mr->tiled = true;
21184
21185 interactFrame->addImage(SDL_Rect{ 24, 96, 0, 14 },
21186 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_B03.png", "interact bottom background");
21187 interactFrame->addImage(SDL_Rect{ 0, 96, 24, 14 },
21188 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_BL03.png", "interact bottom left");
21189 interactFrame->addImage(SDL_Rect{ 0, 96, 24, 14 },
21190 color, "*#images/ui/Inventory/tooltips/HoverItemMenu_BR03.png", "interact bottom right");
21191
21192 auto selectmid = interactFrame->addImage(SDL_Rect{ 6, optionHeight - 16, interactWidth, 22 },
21193 hudColors.itemContextMenuOptionSelectedImg, "*#images/ui/Inventory/tooltips/HoverItemMenu_SelectBack_M03.png", "interact selected highlight mid");
21194 selectmid->tiled = true;
21195 interactFrame->addImage(SDL_Rect{ 6, optionHeight - 16, 20, 22 },
21196 hudColors.itemContextMenuOptionSelectedImg, "*#images/ui/Inventory/tooltips/HoverItemMenu_SelectBack_L03.png", "interact selected highlight left");
21197 interactFrame->addImage(SDL_Rect{ 6, optionHeight - 16, 20, 22 },
21198 hudColors.itemContextMenuOptionSelectedImg, "*#images/ui/Inventory/tooltips/HoverItemMenu_SelectBack_R03.png", "interact selected highlight right");
21199
21200
21201 const char* interactFont = "fonts/pixel_maz.ttf#32#2";
21202
21203 auto interactText = interactFrame->addField("interact text", 32);
21204 interactText->setText(Language::get(4040));
21205 interactText->setSize(SDL_Rect{ 0, 2, 0, topBackgroundHeight });
21206 interactText->setFont(interactFont);
21207 interactText->setHJustify(Field::justify_t::CENTER);
21208 interactText->setVJustify(Field::justify_t::CENTER);
21209 interactText->setColor(hudColors.itemContextMenuHeadingText);
21210
21211 const int interactOptionStartX = 4;
21212 const int interactOptionStartY = 33;
21213 const int glyphSize = 20;
21214
21215 auto interactGlyph1 = interactFrame->addImage(
21216 SDL_Rect{ interactOptionStartX + 4, interactOptionStartY + 4, glyphSize, glyphSize },
21217 0xFFFFFFFF, "", "glyph 1");
21218
21219 const int textAlignX = interactGlyph1->pos.x + interactGlyph1->pos.w + 6;
21220 int textAlignY = interactGlyph1->pos.y - 8;
21221 const int textWidth = 80;
21222 const int textHeight = glyphSize + 8;
21223
21224 Uint32 textColor = hudColors.itemContextMenuOptionText;
21225
21226 interactText = interactFrame->addField("interact option 1", 32);
21227 interactText->setText("");
21228 interactText->setSize(SDL_Rect{
21229 textAlignX,
21230 textAlignY,
21231 textWidth, textHeight });
21232 interactText->setFont(interactFont);
21233 interactText->setHJustify(Field::justify_t::LEFT);
21234 interactText->setVJustify(Field::justify_t::CENTER);
21235 interactText->setColor(textColor);
21236
21237 auto interactGlyph2 = interactFrame->addImage(
21238 SDL_Rect{ interactOptionStartX + 4,
21239 interactGlyph1->pos.y + interactGlyph1->pos.h + 4,
21240 glyphSize, glyphSize },
21241 0xFFFFFFFF, "", "glyph 2");
21242
21243 textAlignY = interactGlyph2->pos.y - 10;
21244 interactText = interactFrame->addField("interact option 2", 32);
21245 interactText->setText("");
21246 interactText->setSize(SDL_Rect{
21247 textAlignX,
21248 textAlignY,
21249 textWidth, textHeight });
21250 interactText->setFont(interactFont);
21251 interactText->setHJustify(Field::justify_t::LEFT);
21252 interactText->setVJustify(Field::justify_t::CENTER);
21253 interactText->setColor(textColor);
21254
21255 auto interactGlyph3 = interactFrame->addImage(
21256 SDL_Rect{ interactOptionStartX + 4,
21257 interactGlyph2->pos.y + interactGlyph2->pos.h + 4,
21258 glyphSize, glyphSize },
21259 0xFFFFFFFF, "", "glyph 3");
21260
21261 textAlignY = interactGlyph3->pos.y - 10;
21262 interactText = interactFrame->addField("interact option 3", 32);
21263 interactText->setText("");
21264 interactText->setSize(SDL_Rect{
21265 textAlignX,
21266 textAlignY,
21267 textWidth, textHeight });
21268 interactText->setFont(interactFont);
21269 interactText->setHJustify(Field::justify_t::LEFT);
21270 interactText->setVJustify(Field::justify_t::CENTER);
21271 interactText->setColor(textColor);
21272
21273 auto interactGlyph4 = interactFrame->addImage(
21274 SDL_Rect{ interactOptionStartX + 4,
21275 interactGlyph3->pos.y + interactGlyph3->pos.h + 4,
21276 glyphSize, glyphSize },
21277 0xFFFFFFFF, "", "glyph 4");
21278
21279 textAlignY = interactGlyph4->pos.y - 10;
21280 interactText = interactFrame->addField("interact option 4", 32);
21281 interactText->setText("");
21282 interactText->setSize(SDL_Rect{
21283 textAlignX,
21284 textAlignY,
21285 textWidth, textHeight });
21286 interactText->setFont(interactFont);
21287 interactText->setHJustify(Field::justify_t::LEFT);
21288 interactText->setVJustify(Field::justify_t::CENTER);
21289 interactText->setColor(textColor);
21290
21291 auto interactGlyph5 = interactFrame->addImage(
21292 SDL_Rect{ interactOptionStartX + 4,
21293 interactGlyph4->pos.y + interactGlyph4->pos.h + 4,
21294 glyphSize, glyphSize },
21295 0xFFFFFFFF, "", "glyph 5");
21296
21297 textAlignY = interactGlyph5->pos.y - 10;
21298 interactText = interactFrame->addField("interact option 5", 32);
21299 interactText->setText("");
21300 interactText->setSize(SDL_Rect{
21301 textAlignX,
21302 textAlignY,
21303 textWidth, textHeight });
21304 interactText->setFont(interactFont);
21305 interactText->setHJustify(Field::justify_t::LEFT);
21306 interactText->setVJustify(Field::justify_t::CENTER);
21307 interactText->setColor(textColor);
21308 }
21309
21310 snprintf(name, sizeof(name), "player item prompt %d", player);
21311 if ( auto promptFrame = players[player]->inventoryUI.tooltipContainerFrame->addFrame(name) )
21312 {
21313 players[player]->inventoryUI.tooltipPromptFrame = promptFrame;
21314 const int interactWidth = 0;
21315 SDL_Rect promptSize{ 0, 0, interactWidth + 6 * 2, 100 };
21316 promptFrame->setDisabled(true);
21317 promptFrame->setInheritParentFrameOpacity(false);
21318
21319 Uint32 color = makeColor( 255, 255, 255, 192);
21320
21321 auto middleCenter = promptFrame->addImage(SDL_Rect{ 6, 2, interactWidth, 76 },
21322 color, "*#images/ui/Inventory/tooltips/Hover_C00.png", "interact middle background");
21323 auto middleTop = promptFrame->addImage(SDL_Rect{ 6, 0, interactWidth, 2 },
21324 color, "*#images/ui/Inventory/tooltips/HoverExt_C00.png", "interact middle top background");
21325 auto middleBottom = promptFrame->addImage(SDL_Rect{ 6, 0, interactWidth, 2 },
21326 color, "*#images/ui/Inventory/tooltips/HoverExt_C00.png", "interact middle bottom background");
21327 auto middleLeft = promptFrame->addImage(SDL_Rect{ 0, 0, 6, 76 },
21328 color, "*#images/ui/Inventory/tooltips/HoverExt_L00.png", "interact middle left");
21329 auto middleRight = promptFrame->addImage(SDL_Rect{ interactWidth + 6, 0, 6, 76 },
21330 color, "*#images/ui/Inventory/tooltips/HoverExt_R00.png", "interact middle right");
21331
21332 auto bottomCenter = promptFrame->addImage(SDL_Rect{ 4, 76, interactWidth + 4, 4 },
21333 color, "*#images/ui/Inventory/tooltips/HoverExt_B00.png", "interact bottom background");
21334 auto bottomLeft = promptFrame->addImage(SDL_Rect{ 0, 76, 4, 4 },
21335 color, "*#images/ui/Inventory/tooltips/HoverExt_BL00.png", "interact bottom left");
21336 auto bottomRight = promptFrame->addImage(SDL_Rect{ interactWidth + 4 * 2, 76, 4, 4 },
21337 color, "*#images/ui/Inventory/tooltips/HoverExt_BR00.png", "interact bottom right");
21338
21339 const int interactOptionStartX = 60;
21340 const int interactOptionStartY = 4;
21341 const int glyphSizeH = 24;
21342 const int glyphSizeW = 22;
21343
21344 auto interactGlyph1 = promptFrame->addImage(
21345 SDL_Rect{ interactOptionStartX, interactOptionStartY, glyphSizeW, glyphSizeH },
21346 0xFFFFFFFF, "", "glyph 1");
21347
21348 auto interactGlyph2 = promptFrame->addImage(
21349 SDL_Rect{ interactGlyph1->pos.x - (glyphSizeW / 2),
21350 interactGlyph1->pos.y + glyphSizeH,
21351 glyphSizeW, glyphSizeH },
21352 0xFFFFFFFF, "", "glyph 2");
21353
21354 auto interactGlyph3 = promptFrame->addImage(
21355 SDL_Rect{ interactGlyph1->pos.x + (glyphSizeW + glyphSizeW / 4), interactGlyph1->pos.y, glyphSizeW, glyphSizeH },
21356 0xFFFFFFFF, "", "glyph 3");
21357
21358 auto interactGlyph4 = promptFrame->addImage(
21359 SDL_Rect{ interactGlyph3->pos.x - (glyphSizeW / 2), interactGlyph2->pos.y,
21360 glyphSizeW, glyphSizeH },
21361 0xFFFFFFFF, "", "glyph 4");
21362
21363 auto grabIcon = promptFrame->addImage(
21364 SDL_Rect{ 0, 0, 0, 0 },
21365 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/Inventory_Grab.png", "grab icon");
21366 grabIcon->disabled = true;
21367 auto dropIcon = promptFrame->addImage(
21368 SDL_Rect{ 0, 0, 0, 0 },
21369 0xFFFFFFFF, "*#images/ui/Inventory/tooltips/Inventory_Drop.png", "drop icon");
21370 dropIcon->disabled = true;
21371
21372 const int textWidth = 200;
21373 const int textHeight = glyphSizeH + 8;
21374
21375 Uint32 promptTextColor = makeColor( 188, 154, 114, 255);
21376 const char * promptFont = "fonts/pixel_maz.ttf#32#2";
21377 int textAlignY = interactGlyph1->pos.y - 4;
21378 int textAlignXRightJustify = interactGlyph1->pos.x - 6 - textWidth;
21379 auto promptText = promptFrame->addField("txt 1", 32);
21380 promptText->setText(Language::get(4050));
21381 promptText->setSize(SDL_Rect{
21382 textAlignXRightJustify,
21383 textAlignY,
21384 textWidth, textHeight });
21385 promptText->setFont(promptFont);
21386 promptText->setHJustify(Field::justify_t::RIGHT);
21387 promptText->setVJustify(Field::justify_t::CENTER);
21388 promptText->setColor(promptTextColor);
21389
21390 textAlignXRightJustify = interactGlyph2->pos.x - 6 - textWidth;
21391 textAlignY = interactGlyph2->pos.y - 4;
21392 promptText = promptFrame->addField("txt 2", 32);
21393 promptText->setText(Language::get(4040));
21394 promptText->setSize(SDL_Rect{
21395 textAlignXRightJustify,
21396 textAlignY,
21397 textWidth, textHeight });
21398 promptText->setFont(promptFont);
21399 promptText->setHJustify(Field::justify_t::RIGHT);
21400 promptText->setVJustify(Field::justify_t::CENTER);
21401 promptText->setColor(promptTextColor);
21402
21403 int textAlignXLeftJustify = interactGlyph3->pos.x + interactGlyph3->pos.w + 6;
21404 textAlignY = interactGlyph3->pos.y - 4;
21405 promptText = promptFrame->addField("txt 3", 32);
21406 promptText->setText(Language::get(5956));
21407 promptText->setSize(SDL_Rect{
21408 textAlignXLeftJustify,
21409 textAlignY,
21410 textWidth, textHeight });
21411 promptText->setFont(promptFont);
21412 promptText->setHJustify(Field::justify_t::LEFT);
21413 promptText->setVJustify(Field::justify_t::CENTER);
21414 promptText->setColor(promptTextColor);
21415
21416 textAlignXLeftJustify = interactGlyph4->pos.x + interactGlyph4->pos.w + 6;
21417 textAlignY = interactGlyph4->pos.y - 4;
21418 promptText = promptFrame->addField("txt 4", 32);
21419 promptText->setText(Language::get(5957));
21420 promptText->setSize(SDL_Rect{
21421 textAlignXLeftJustify,
21422 textAlignY,
21423 textWidth, textHeight });
21424 promptText->setFont(promptFont);
21425 promptText->setHJustify(Field::justify_t::LEFT);
21426 promptText->setVJustify(Field::justify_t::CENTER);
21427 promptText->setColor(promptTextColor);
21428
21429 bottomCenter->pos.y = interactGlyph4->pos.y + interactGlyph4->pos.h + 2;
21430 bottomLeft->pos.y = bottomCenter->pos.y;
21431 bottomRight->pos.y = bottomCenter->pos.y;
21432
21433 promptSize.h = bottomCenter->pos.y + bottomCenter->pos.h;
21434
21435 promptFrame->setSize(promptSize);
21436 middleCenter->pos.h = bottomCenter->pos.y - 4;
21437 middleLeft->pos.h = bottomCenter->pos.y;
21438 middleRight->pos.h = bottomCenter->pos.y;
21439 middleBottom->pos.y = bottomCenter->pos.y - 2;
21440 }
21441}
21442
21443view_t playerPortraitView[MAXPLAYERS4];
21444
21445void drawCharacterPreview(const int player, SDL_Rect pos, int fov, real_t offsetyaw, bool dark)
21446{
21447 if (player < 0 || player >= MAXPLAYERS4) {
21448 return;
21449 }
21450 view_t& view = playerPortraitView[player];
21451 auto ofov = ::fov;
21452 ::fov = fov;
21453
21454 //TempTexture* minimapTexture = new TempTexture();
21455 auto playerEntity = Player::getPlayerInteractEntity(player);
21456
21457 if ( playerEntity )
21458 {
21459 GL_CHECK_ERR(glClear(GL_DEPTH_BUFFER_BIT))({ glClear(0x00000100); GLenum err; while((err = glGetError()
) != 0) { printlog("[OpenGL]: ERROR type = 0x%x, message = %s"
, err, gl_error_string(err)); }})
;
21460
21461 static ConsoleVariable<bool> cvar_char_portrait_static_angle("/char_portrait_static_angle", true);
21462 view.x = playerEntity->x / 16.0 + (.92 * cos(offsetyaw
21463 + (*cvar_char_portrait_static_angle ? playerEntity->yaw : 0)));
21464 view.y = playerEntity->y / 16.0 + (.92 * sin(offsetyaw
21465 + (*cvar_char_portrait_static_angle ? playerEntity->yaw : 0)));
21466 view.z = playerEntity->z * 2;
21467 view.ang = (offsetyaw - PI3.14159265358979323846
21468 + (*cvar_char_portrait_static_angle ? playerEntity->yaw : 0)); //5 * PI / 4;
21469 view.vang = PI3.14159265358979323846 / 20;
21470
21471 view.winx = pos.x;
21472 // winy modification required due to new frame scaling method d49b1a5f34667432f2a2bd754c0abca3a09227c8
21473 view.winy = pos.y + (yres - Frame::virtualScreenY);
21474 //view.winx = x1 + 8;
21475 //view.winy = y1 + 8;
21476
21477 view.winw = pos.w;
21478 view.winh = pos.h;
21479 glBeginCamera(&view, false);
21480 bool b = playerEntity->flags[BRIGHT1];
21481 if (!dark) { playerEntity->flags[BRIGHT1] = true; }
21482 if ( !playerEntity->flags[INVISIBLE2] )
21483 {
21484 glDrawVoxel(&view, playerEntity, REALCOLORS0);
21485 }
21486 playerEntity->flags[BRIGHT1] = b;
21487 int c = 0;
21488 if ( multiplayer != CLIENT2 )
21489 {
21490 for ( node_t* node = playerEntity->children.first; node != nullptr; node = node->next )
21491 {
21492 if ( playerEntity->behavior == &actPlayer )
21493 {
21494 if ( c == 0 )
21495 {
21496 c++;
21497 continue;
21498 }
21499 }
21500 Entity* entity = (Entity*)node->element;
21501 if ( !entity->flags[INVISIBLE2] )
21502 {
21503 bool b = entity->flags[BRIGHT1];
21504 if (!dark) { entity->flags[BRIGHT1] = true; }
21505 glDrawVoxel(&view, entity, REALCOLORS0);
21506 entity->flags[BRIGHT1] = b;
21507 }
21508 c++;
21509 }
21510 for ( node_t* node = map.entities->first; node != NULL__null; node = node->next )
21511 {
21512 Entity* entity = (Entity*)node->element;
21513 if ( (Sint32)entity->getUID() == -4 ) // torch sprites
21514 {
21515 if ( (entity->skill[1] - 1) != player )
21516 {
21517 continue;
21518 }
21519 bool b = entity->flags[BRIGHT1];
21520 if (!dark) { entity->flags[BRIGHT1] = true; }
21521 glDrawSprite(&view, entity, REALCOLORS0);
21522 entity->flags[BRIGHT1] = b;
21523 }
21524 }
21525 }
21526 else
21527 {
21528 for ( node_t* node = map.entities->first; node != NULL__null; node = node->next )
21529 {
21530 Entity* entity = (Entity*)node->element;
21531 if ( playerEntity->behavior == &actPlayer )
21532 {
21533 if ( (entity->behavior == &actPlayerLimb && entity->skill[2] == player && !entity->flags[INVISIBLE2]) || (Sint32)entity->getUID() == -4 )
21534 {
21535 if ( (Sint32)entity->getUID() == -4 ) // torch sprites
21536 {
21537 if ( (entity->skill[1] - 1) != player )
21538 {
21539 continue;
21540 }
21541 bool b = entity->flags[BRIGHT1];
21542 if (!dark) { entity->flags[BRIGHT1] = true; }
21543 glDrawSprite(&view, entity, REALCOLORS0);
21544 entity->flags[BRIGHT1] = b;
21545 }
21546 else
21547 {
21548 bool b = entity->flags[BRIGHT1];
21549 if (!dark) { entity->flags[BRIGHT1] = true; }
21550 glDrawVoxel(&view, entity, REALCOLORS0);
21551 entity->flags[BRIGHT1] = b;
21552 }
21553 }
21554 }
21555 else if ( playerEntity->behavior == &actDeathGhost )
21556 {
21557 if ( entity->behavior == &actDeathGhostLimb && entity->skill[2] == player && !entity->flags[INVISIBLE2] )
21558 {
21559 bool b = entity->flags[BRIGHT1];
21560 if ( !dark ) { entity->flags[BRIGHT1] = true; }
21561 glDrawVoxel(&view, entity, REALCOLORS0);
21562 entity->flags[BRIGHT1] = b;
21563 }
21564 }
21565 }
21566 }
21567 if (drawingGui) {
21568 // blending gets disabled after objects are drawn, so re-enable it.
21569 GL_CHECK_ERR(glEnable(GL_BLEND))({ glEnable(0x0BE2); GLenum err; while((err = glGetError()) !=
0) { printlog("[OpenGL]: ERROR type = 0x%x, message = %s", err
, gl_error_string(err)); }})
;
21570 }
21571 glEndCamera(&view, false);
21572 }
21573 ::fov = ofov;
21574}
21575
21576Player::SkillSheet_t::SkillSheetData_t Player::SkillSheet_t::skillSheetData;
21577void Player::SkillSheet_t::loadSkillSheetJSON()
21578{
21579 if ( !PHYSFS_getRealDir("/data/skillsheet_entries.json") )
21580 {
21581 printlog("[JSON]: Error: Could not find file: data/skillsheet_entries.json");
21582 }
21583 else
21584 {
21585 std::string inputPath = PHYSFS_getRealDir("/data/skillsheet_entries.json");
21586 inputPath.append("/data/skillsheet_entries.json");
21587
21588 File* fp = FileIO::open(inputPath.c_str(), "rb");
21589 if ( !fp )
21590 {
21591 printlog("[JSON]: Error: Could not open json file %s", inputPath.c_str());
21592 }
21593 else
21594 {
21595 char buf[65536];
21596 int count = fp->read(buf, sizeof(buf[0]), sizeof(buf));
21597 buf[count] = '\0';
21598 rapidjson::StringStream is(buf);
21599 FileIO::close(fp);
21600 rapidjson::Document d;
21601 d.ParseStream(is);
21602 if ( !d.HasMember("version") )
21603 {
21604 printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str());
21605 }
21606 else
21607 {
21608 if ( d.HasMember("skills") )
21609 {
21610 auto& allEntries = skillSheetData.skillEntries;
21611 allEntries.clear();
21612 for ( rapidjson::Value::ConstValueIterator itr = d["skills"].Begin();
21613 itr != d["skills"].End(); ++itr )
21614 {
21615 allEntries.push_back(SkillSheetData_t::SkillEntry_t());
21616 auto& entry = allEntries[allEntries.size() - 1];
21617 if ( (*itr).HasMember("name") )
21618 {
21619 entry.name = (*itr)["name"].GetString();
21620 }
21621 if ( (*itr).HasMember("id") )
21622 {
21623 entry.skillId = (*itr)["id"].GetInt();
21624 }
21625 if ( (*itr).HasMember("sfx") )
21626 {
21627 entry.skillSfx = (*itr)["sfx"].GetInt();
21628 }
21629 if ( (*itr).HasMember("icon_base_path") )
21630 {
21631 entry.skillIconPath = (*itr)["icon_base_path"].GetString();
21632 }
21633 if ( (*itr).HasMember("icon_legend_path") )
21634 {
21635 entry.skillIconPathLegend = (*itr)["icon_legend_path"].GetString();
21636 }
21637 if ( (*itr).HasMember("icon_base_path_32px") )
21638 {
21639 entry.skillIconPath32px = (*itr)["icon_base_path_32px"].GetString();
21640 }
21641 if ( (*itr).HasMember("icon_legend_path_32px") )
21642 {
21643 entry.skillIconPathLegend32px = (*itr)["icon_legend_path_32px"].GetString();
21644 }
21645 if ( (*itr).HasMember("icon_stat_path") )
21646 {
21647 entry.statIconPath = (*itr)["icon_stat_path"].GetString();
21648 }
21649 if ( (*itr).HasMember("description") )
21650 {
21651 entry.description = (*itr)["description"].GetString();
21652 }
21653 if ( (*itr).HasMember("legend_text") )
21654 {
21655 entry.legendaryDescription = (*itr)["legend_text"].GetString();
21656 }
21657 if ( (*itr).HasMember("effects") )
21658 {
21659 for ( rapidjson::Value::ConstValueIterator eff_itr = (*itr)["effects"].Begin(); eff_itr != (*itr)["effects"].End(); ++eff_itr )
21660 {
21661 entry.effects.push_back(SkillSheetData_t::SkillEntry_t::SkillEffect_t());
21662 auto& effect = entry.effects[entry.effects.size() - 1];
21663 effect.tag = (*eff_itr)["tag"].GetString();
21664 effect.title = (*eff_itr)["title"].GetString();
21665 effect.rawValue = (*eff_itr)["value"].GetString();
21666 effect.valueCustomWidthOffset = 0;
21667 if ( (*eff_itr).HasMember("custom_value_width_offset") )
21668 {
21669 effect.valueCustomWidthOffset = (*eff_itr)["custom_value_width_offset"].GetInt();
21670 }
21671 if ( (*eff_itr).HasMember("auto_resize_value") )
21672 {
21673 effect.bAllowAutoResizeValue = (*eff_itr)["auto_resize_value"].GetBool();
21674 }
21675 else
21676 {
21677 effect.bAllowAutoResizeValue = false;
21678 }
21679 if ( (*eff_itr).HasMember("realtime_update") )
21680 {
21681 effect.bAllowRealtimeUpdate = (*eff_itr)["realtime_update"].GetBool();
21682 }
21683 else
21684 {
21685 effect.bAllowRealtimeUpdate = false;
21686 }
21687 }
21688 }
21689 if ( (*itr).HasMember("effects_position") )
21690 {
21691 for ( rapidjson::Value::ConstMemberIterator effPos_itr = (*itr)["effects_position"].MemberBegin();
21692 effPos_itr != (*itr)["effects_position"].MemberEnd(); ++effPos_itr )
21693 {
21694 std::string memberName = effPos_itr->name.GetString();
21695 if ( memberName == "effect_start_offset_x" )
21696 {
21697 entry.effectStartOffsetX = effPos_itr->value.GetInt();
21698 }
21699 else if ( memberName == "effect_background_offset_x" )
21700 {
21701 entry.effectBackgroundOffsetX = effPos_itr->value.GetInt();
21702 }
21703 else if ( memberName == "effect_background_width" )
21704 {
21705 entry.effectBackgroundWidth = effPos_itr->value.GetInt();
21706 }
21707 }
21708 }
21709 }
21710 }
21711 if ( d.HasMember("window_scaling") )
21712 {
21713 if ( d["window_scaling"].HasMember("standard_scale_modifier_x") )
21714 {
21715 windowHeightScaleX = d["window_scaling"]["standard_scale_modifier_x"].GetDouble();
21716 }
21717 if ( d["window_scaling"].HasMember("standard_scale_modifier_y") )
21718 {
21719 windowHeightScaleY = d["window_scaling"]["standard_scale_modifier_y"].GetDouble();
21720 }
21721 if ( d["window_scaling"].HasMember("compact_scale_modifier_x") )
21722 {
21723 windowCompactHeightScaleX = d["window_scaling"]["compact_scale_modifier_x"].GetDouble();
21724 }
21725 if ( d["window_scaling"].HasMember("compact_scale_modifier_y") )
21726 {
21727 windowCompactHeightScaleY = d["window_scaling"]["compact_scale_modifier_y"].GetDouble();
21728 }
21729 }
21730 if ( d.HasMember("skill_select_images") )
21731 {
21732 if ( d["skill_select_images"].HasMember("highlight_left") )
21733 {
21734 skillSheetData.highlightSkillImg = d["skill_select_images"]["highlight_left"].GetString();
21735 }
21736 if ( d["skill_select_images"].HasMember("selected_left") )
21737 {
21738 skillSheetData.selectSkillImg = d["skill_select_images"]["selected_left"].GetString();
21739 }
21740 if ( d["skill_select_images"].HasMember("highlight_right") )
21741 {
21742 skillSheetData.highlightSkillImg_Right = d["skill_select_images"]["highlight_right"].GetString();
21743 }
21744 if ( d["skill_select_images"].HasMember("selected_right") )
21745 {
21746 skillSheetData.selectSkillImg_Right = d["skill_select_images"]["selected_right"].GetString();
21747 }
21748 }
21749 if ( d.HasMember("skill_background_icons") )
21750 {
21751 if ( d["skill_background_icons"].HasMember("default") )
21752 {
21753 skillSheetData.iconBgPathDefault = d["skill_background_icons"]["default"].GetString();
21754 }
21755 if ( d["skill_background_icons"].HasMember("default_selected") )
21756 {
21757 skillSheetData.iconBgSelectedPathDefault = d["skill_background_icons"]["default_selected"].GetString();
21758 }
21759 if ( d["skill_background_icons"].HasMember("novice") )
21760 {
21761 skillSheetData.iconBgPathNovice = d["skill_background_icons"]["novice"].GetString();
21762 }
21763 if ( d["skill_background_icons"].HasMember("novice_selected") )
21764 {
21765 skillSheetData.iconBgSelectedPathNovice = d["skill_background_icons"]["novice_selected"].GetString();
21766 }
21767 if ( d["skill_background_icons"].HasMember("expert") )
21768 {
21769 skillSheetData.iconBgPathExpert = d["skill_background_icons"]["expert"].GetString();
21770 }
21771 if ( d["skill_background_icons"].HasMember("expert_selected") )
21772 {
21773 skillSheetData.iconBgSelectedPathExpert = d["skill_background_icons"]["expert_selected"].GetString();
21774 }
21775 if ( d["skill_background_icons"].HasMember("legend") )
21776 {
21777 skillSheetData.iconBgPathLegend = d["skill_background_icons"]["legend"].GetString();
21778 }
21779 if ( d["skill_background_icons"].HasMember("legend_selected") )
21780 {
21781 skillSheetData.iconBgSelectedPathLegend = d["skill_background_icons"]["legend_selected"].GetString();
21782 }
21783 }
21784 if ( d.HasMember("alchemy_potion_names_to_filter") )
21785 {
21786 skillSheetData.potionNamesToFilter.clear();
21787 if ( d["alchemy_potion_names_to_filter"].IsString() )
21788 {
21789 skillSheetData.potionNamesToFilter.push_back(d["alchemy_potion_names_to_filter"].GetString());
21790 }
21791 else if ( d["alchemy_potion_names_to_filter"].IsArray() )
21792 {
21793 for ( rapidjson::Value::ConstValueIterator pot_itr = d["alchemy_potion_names_to_filter"].Begin();
21794 pot_itr != d["alchemy_potion_names_to_filter"].End(); ++pot_itr )
21795 {
21796 skillSheetData.potionNamesToFilter.push_back(pot_itr->GetString());
21797 }
21798 }
21799 }
21800 if ( d.HasMember("colors") )
21801 {
21802 if ( d["colors"].HasMember("default") )
21803 {
21804 skillSheetData.defaultTextColor = makeColor(
21805 d["colors"]["default"]["r"].GetInt(),
21806 d["colors"]["default"]["g"].GetInt(),
21807 d["colors"]["default"]["b"].GetInt(),
21808 d["colors"]["default"]["a"].GetInt());
21809 }
21810 if ( d["colors"].HasMember("novice") )
21811 {
21812 skillSheetData.noviceTextColor = makeColor(
21813 d["colors"]["novice"]["r"].GetInt(),
21814 d["colors"]["novice"]["g"].GetInt(),
21815 d["colors"]["novice"]["b"].GetInt(),
21816 d["colors"]["novice"]["a"].GetInt());
21817 }
21818 if ( d["colors"].HasMember("expert") )
21819 {
21820 skillSheetData.expertTextColor = makeColor(
21821 d["colors"]["expert"]["r"].GetInt(),
21822 d["colors"]["expert"]["g"].GetInt(),
21823 d["colors"]["expert"]["b"].GetInt(),
21824 d["colors"]["expert"]["a"].GetInt());
21825 }
21826 if ( d["colors"].HasMember("legend") )
21827 {
21828 skillSheetData.legendTextColor = makeColor(
21829 d["colors"]["legend"]["r"].GetInt(),
21830 d["colors"]["legend"]["g"].GetInt(),
21831 d["colors"]["legend"]["b"].GetInt(),
21832 d["colors"]["legend"]["a"].GetInt());
21833 }
21834 }
21835 printlog("[JSON]: Successfully read json file %s", inputPath.c_str());
21836 }
21837 }
21838 }
21839
21840 if ( !PHYSFS_getRealDir("/data/skillsheet_leadership_entries.json") )
21841 {
21842 printlog("[JSON]: Error: Could not find file: data/skillsheet_leadership_entries.json");
21843 }
21844 else
21845 {
21846 std::string inputPath = PHYSFS_getRealDir("/data/skillsheet_leadership_entries.json");
21847 inputPath.append("/data/skillsheet_leadership_entries.json");
21848
21849 File* fp = FileIO::open(inputPath.c_str(), "rb");
21850 if ( !fp )
21851 {
21852 printlog("[JSON]: Error: Could not open json file %s", inputPath.c_str());
21853 }
21854 else
21855 {
21856 char buf[65536];
21857 int count = fp->read(buf, sizeof(buf[0]), sizeof(buf));
21858 buf[count] = '\0';
21859 rapidjson::StringStream is(buf);
21860 FileIO::close(fp);
21861
21862 rapidjson::Document d;
21863 d.ParseStream(is);
21864 if ( !d.HasMember("version") )
21865 {
21866 printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str());
21867 }
21868 else
21869 {
21870 if ( d.HasMember("leadership_allies_base") )
21871 {
21872 auto& allyTable = skillSheetData.leadershipAllyTableBase;
21873 allyTable.clear();
21874 for ( rapidjson::Value::ConstMemberIterator itr = d["leadership_allies_base"].MemberBegin();
21875 itr != d["leadership_allies_base"].MemberEnd(); ++itr )
21876 {
21877 std::string monsterName = itr->name.GetString();
21878 int monsterType = -1;
21879 for ( int i = 0; i < NUMMONSTERS; ++i )
21880 {
21881 if ( monsterName.compare(monstertypename[i]) == 0 )
21882 {
21883 monsterType = i;
21884 break;
21885 }
21886 }
21887 if ( monsterType < 0 ) { continue; }
21888 if ( itr->value.IsArray() )
21889 {
21890 for ( rapidjson::Value::ConstValueIterator ally_itr = itr->value.Begin();
21891 ally_itr != itr->value.End(); ++ally_itr )
21892 {
21893 std::string allyName = ally_itr->GetString();
21894 int allyType = -1;
21895 for ( int i = 0; i < NUMMONSTERS; ++i )
21896 {
21897 if ( allyName.compare(monstertypename[i]) == 0 )
21898 {
21899 allyType = i;
21900 break;
21901 }
21902 }
21903 if ( allyType >= 0 )
21904 {
21905 allyTable[(Monster)monsterType].push_back((Monster)allyType);
21906 }
21907 }
21908 }
21909 }
21910 }
21911 if ( d.HasMember("leadership_allies_legendary") )
21912 {
21913 auto& allyTable = skillSheetData.leadershipAllyTableLegendary;
21914 allyTable.clear();
21915 for ( rapidjson::Value::ConstMemberIterator itr = d["leadership_allies_legendary"].MemberBegin();
21916 itr != d["leadership_allies_legendary"].MemberEnd(); ++itr )
21917 {
21918 std::string monsterName = itr->name.GetString();
21919 int monsterType = -1;
21920 for ( int i = 0; i < NUMMONSTERS; ++i )
21921 {
21922 if ( monsterName.compare(monstertypename[i]) == 0 )
21923 {
21924 monsterType = i;
21925 break;
21926 }
21927 }
21928 if ( monsterType < 0 ) { continue; }
21929 if ( itr->value.IsArray() )
21930 {
21931 for ( rapidjson::Value::ConstValueIterator ally_itr = itr->value.Begin();
21932 ally_itr != itr->value.End(); ++ally_itr )
21933 {
21934 std::string allyName = ally_itr->GetString();
21935 int allyType = -1;
21936 for ( int i = 0; i < NUMMONSTERS; ++i )
21937 {
21938 if ( allyName.compare(monstertypename[i]) == 0 )
21939 {
21940 allyType = i;
21941 break;
21942 }
21943 }
21944 if ( allyType >= 0 )
21945 {
21946 allyTable[(Monster)monsterType].push_back((Monster)allyType);
21947 }
21948 }
21949 }
21950 }
21951 }
21952 if ( d.HasMember("leadership_allies_unique_recruits") )
21953 {
21954 auto& allyTable = skillSheetData.leadershipAllyTableSpecialRecruitment;
21955 allyTable.clear();
21956 for ( rapidjson::Value::ConstMemberIterator itr = d["leadership_allies_unique_recruits"].MemberBegin();
21957 itr != d["leadership_allies_unique_recruits"].MemberEnd(); ++itr )
21958 {
21959 std::string monsterName = itr->name.GetString();
21960 int monsterType = -1;
21961 for ( int i = 0; i < NUMMONSTERS; ++i )
21962 {
21963 if ( monsterName.compare(monstertypename[i]) == 0 )
21964 {
21965 monsterType = i;
21966 break;
21967 }
21968 }
21969 if ( monsterType < 0 ) { continue; }
21970 if ( itr->value.IsArray() )
21971 {
21972 for ( rapidjson::Value::ConstValueIterator ally_itr = itr->value.Begin();
21973 ally_itr != itr->value.End(); ++ally_itr )
21974 {
21975 for ( rapidjson::Value::ConstMemberIterator entry_itr = ally_itr->MemberBegin();
21976 entry_itr != ally_itr->MemberEnd(); ++entry_itr )
21977 {
21978 std::string allyName = entry_itr->name.GetString();
21979 int allyType = -1;
21980 for ( int i = 0; i < NUMMONSTERS; ++i )
21981 {
21982 if ( allyName.compare(monstertypename[i]) == 0 )
21983 {
21984 allyType = i;
21985 break;
21986 }
21987 }
21988 if ( allyType >= 0 )
21989 {
21990 allyTable[(Monster)monsterType].push_back(std::make_pair((Monster)allyType, entry_itr->value.GetString()));
21991 }
21992 }
21993 }
21994 }
21995 }
21996 }
21997 printlog("[JSON]: Successfully read json file %s", inputPath.c_str());
21998 }
21999 }
22000 }
22001}
22002
22003void loadHUDSettingsJSON()
22004{
22005 if ( !PHYSFS_getRealDir("/data/HUD_settings.json") )
22006 {
22007 printlog("[JSON]: Error: Could not find file: data/HUD_settings.json");
22008 }
22009 else
22010 {
22011 std::string inputPath = PHYSFS_getRealDir("/data/HUD_settings.json");
22012 inputPath.append("/data/HUD_settings.json");
22013
22014 File* fp = FileIO::open(inputPath.c_str(), "rb");
22015 if ( !fp )
22016 {
22017 printlog("[JSON]: Error: Could not open json file %s", inputPath.c_str());
22018 }
22019 else
22020 {
22021 char buf[65536];
22022 int count = fp->read(buf, sizeof(buf[0]), sizeof(buf));
22023 buf[count] = '\0';
22024 rapidjson::StringStream is(buf);
22025 FileIO::close(fp);
22026
22027 rapidjson::Document d;
22028 d.ParseStream(is);
22029 if ( !d.HasMember("version") )
22030 {
22031 printlog("[JSON]: Error: No 'version' value in json file, or JSON syntax incorrect! %s", inputPath.c_str());
22032 }
22033 else
22034 {
22035 if ( d.HasMember("selected_cursor_opacity") )
22036 {
22037 selectedCursorOpacity = d["selected_cursor_opacity"].GetInt();
22038 }
22039 if ( d.HasMember("selected_old_cursor_opacity") )
22040 {
22041 oldSelectedCursorOpacity = d["selected_old_cursor_opacity"].GetInt();
22042 }
22043 if ( d.HasMember("hotbar") )
22044 {
22045 if ( d["hotbar"].HasMember("hotbar_slot_opacity") )
22046 {
22047 hotbarSlotOpacity = d["hotbar"]["hotbar_slot_opacity"].GetInt();
22048 }
22049 if ( d["hotbar"].HasMember("hotbar_selected_slot_opacity") )
22050 {
22051 hotbarSelectedSlotOpacity = d["hotbar"]["hotbar_selected_slot_opacity"].GetInt();
22052 }
22053 if ( d["hotbar"].HasMember("hotbar_compact_x_offset") )
22054 {
22055 hotbarCompactOffsetX = d["hotbar"]["hotbar_compact_x_offset"].GetInt();
22056 }
22057 if ( d["hotbar"].HasMember("hotbar_compact_slot_overlap") )
22058 {
22059 hotbarCompactSlotOverlapPercent = d["hotbar"]["hotbar_compact_slot_overlap"].GetDouble();
22060 }
22061 if ( d["hotbar"].HasMember("hotbar_compact_inactive_slot_movement_x") )
22062 {
22063 hotbarCompactInactiveSlotMovementX = d["hotbar"]["hotbar_compact_inactive_slot_movement_x"].GetInt();
22064 }
22065 if ( d["hotbar"].HasMember("hotbar_compact_expanded_x_offset") )
22066 {
22067 hotbarCompactExpandedOffsetX = d["hotbar"]["hotbar_compact_expanded_x_offset"].GetInt();
22068 }
22069 if ( d["hotbar"].HasMember("hotbar_y_offset") )
22070 {
22071 hotbarOffsetY = d["hotbar"]["hotbar_y_offset"].GetInt();
22072 }
22073 if ( d["hotbar"].HasMember("hotbar_compact_y_offset") )
22074 {
22075 hotbarCompactOffsetY = d["hotbar"]["hotbar_compact_y_offset"].GetInt();
22076 }
22077 }
22078 if ( d.HasMember("xpbar") )
22079 {
22080 if ( d["xpbar"].HasMember("xpbar_width_offset") )
22081 {
22082 xpbarOffsetWidth = d["xpbar"]["xpbar_width_offset"].GetInt();
22083 }
22084 if ( d["xpbar"].HasMember("xpbar_compact_width_offset") )
22085 {
22086 xpbarCompactOffsetWidth = d["xpbar"]["xpbar_compact_width_offset"].GetInt();
22087 }
22088 if ( d["xpbar"].HasMember("xpbar_y_offset") )
22089 {
22090 xpbarOffsetY = d["xpbar"]["xpbar_y_offset"].GetInt();
22091 }
22092 if ( d["xpbar"].HasMember("xpbar_compact_y_offset") )
22093 {
22094 xpbarCompactOffsetY = d["xpbar"]["xpbar_compact_y_offset"].GetInt();
22095 }
22096 }
22097 if ( d.HasMember("hpmpbar") )
22098 {
22099 if ( d["hpmpbar"].HasMember("hpmpbar_width_offset") )
22100 {
22101 hpmpbarOffsetWidth = d["hpmpbar"]["hpmpbar_width_offset"].GetInt();
22102 }
22103 if ( d["hpmpbar"].HasMember("hpmpbar_x_offset") )
22104 {
22105 hpmpbarOffsetX = d["hpmpbar"]["hpmpbar_x_offset"].GetInt();
22106 }
22107 if ( d["hpmpbar"].HasMember("hpmpbar_y_offset") )
22108 {
22109 hpmpbarOffsetY = d["hpmpbar"]["hpmpbar_y_offset"].GetInt();
22110 }
22111 if ( d["hpmpbar"].HasMember("hpmpbar_compact_width_offset") )
22112 {
22113 hpmpbarCompactOffsetWidth = d["hpmpbar"]["hpmpbar_compact_width_offset"].GetInt();
22114 }
22115 if ( d["hpmpbar"].HasMember("hpmpbar_compact_x_offset") )
22116 {
22117 hpmpbarCompactOffsetX = d["hpmpbar"]["hpmpbar_compact_x_offset"].GetInt();
22118 }
22119 if ( d["hpmpbar"].HasMember("hpmpbar_compact_y_offset") )
22120 {
22121 hpmpbarCompactOffsetY = d["hpmpbar"]["hpmpbar_compact_y_offset"].GetInt();
22122 }
22123
22124 if ( d["hpmpbar"].HasMember("hpmpbar_max_amount_threshold") )
22125 {
22126 hpmpbarMaxWidthAmount = d["hpmpbar"]["hpmpbar_max_amount_threshold"].GetInt();
22127 }
22128 if ( d["hpmpbar"].HasMember("hpmpbar_interval_to_increase_width") )
22129 {
22130 hpmpbarIntervalToIncreaseWidth
22131 = d["hpmpbar"]["hpmpbar_interval_to_increase_width"].GetInt();
22132 }
22133 if ( d["hpmpbar"].HasMember("hpmpbar_base_percent") )
22134 {
22135 hpmpbarBasePercentSize = d["hpmpbar"]["hpmpbar_base_percent"].GetInt();
22136 }
22137 if ( d["hpmpbar"].HasMember("hpmpbar_interval_start_value") )
22138 {
22139 hpmpbarIntervalStartValue = d["hpmpbar"]["hpmpbar_interval_start_value"].GetInt();
22140 }
22141 if ( d["hpmpbar"].HasMember("hpmpbar_width_increase_percent_on_interval") )
22142 {
22143 hpmpbarWidthIncreasePercentOnInterval =
22144 d["hpmpbar"]["hpmpbar_width_increase_percent_on_interval"].GetDouble();
22145 }
22146
22147 if ( d["hpmpbar"].HasMember("hpmpbar_compact_max_amount_threshold") )
22148 {
22149 hpmpbarCompactMaxWidthAmount = d["hpmpbar"]["hpmpbar_compact_max_amount_threshold"].GetInt();
22150 }
22151 if ( d["hpmpbar"].HasMember("hpmpbar_compact_interval_to_increase_width") )
22152 {
22153 hpmpbarCompactIntervalToIncreaseWidth =
22154 d["hpmpbar"]["hpmpbar_compact_interval_to_increase_width"].GetInt();
22155 }
22156 if ( d["hpmpbar"].HasMember("hpmpbar_compact_base_percent") )
22157 {
22158 hpmpbarCompactBasePercentSize = d["hpmpbar"]["hpmpbar_compact_base_percent"].GetInt();
22159 }
22160 if ( d["hpmpbar"].HasMember("hpmpbar_compact_interval_start_value") )
22161 {
22162 hpmpbarCompactIntervalStartValue = d["hpmpbar"]["hpmpbar_compact_interval_start_value"].GetInt();
22163 }
22164 if ( d["hpmpbar"].HasMember("hpmpbar_compact_width_increase_percent_on_interval") )
22165 {
22166 hpmpbarCompactWidthIncreasePercentOnInterval =
22167 d["hpmpbar"]["hpmpbar_compact_width_increase_percent_on_interval"].GetDouble();
22168 }
22169 }
22170 if ( d.HasMember("allybars") )
22171 {
22172 for ( auto ally_itr = d["allybars"].MemberBegin(); ally_itr != d["allybars"].MemberEnd(); ++ally_itr )
22173 {
22174 std::string type = ally_itr->name.GetString();
22175 if ( type == "followers" )
22176 {
22177 if ( ally_itr->value.HasMember("entry_height") )
22178 {
22179 AllyStatusBarSettings_t::FollowerBars_t::entrySettings.entryHeight = ally_itr->value["entry_height"].GetInt();
22180 }
22181 if ( ally_itr->value.HasMember("entry_compact_height") )
22182 {
22183 AllyStatusBarSettings_t::FollowerBars_t::entrySettings.entryCompactHeight = ally_itr->value["entry_compact_height"].GetInt();
22184 }
22185 if ( ally_itr->value.HasMember("infinite_scroll") )
22186 {
22187 Player::HUD_t::FollowerDisplay_t::infiniteScrolling = ally_itr->value["infinite_scroll"].GetBool();
22188 }
22189 if ( ally_itr->value.HasMember("max_finite_bars") )
22190 {
22191 Player::HUD_t::FollowerDisplay_t::numFiniteBars = ally_itr->value["max_finite_bars"].GetInt();
22192 }
22193 if ( ally_itr->value.HasMember("max_fullsize_bars") )
22194 {
22195 Player::HUD_t::FollowerDisplay_t::numInfiniteFullsizeBars = ally_itr->value["max_fullsize_bars"].GetInt();
22196 }
22197 if ( ally_itr->value.HasMember("max_compact_bars") )
22198 {
22199 Player::HUD_t::FollowerDisplay_t::numInfiniteCompactBars = ally_itr->value["max_compact_bars"].GetInt();
22200 }
22201 if ( ally_itr->value.HasMember("splitscreen_max_fullsize_bars") )
22202 {
22203 Player::HUD_t::FollowerDisplay_t::numInfiniteSplitscreenFullsizeBars = ally_itr->value["splitscreen_max_fullsize_bars"].GetInt();
22204 }
22205 if ( ally_itr->value.HasMember("splitscreen_max_compact_bars") )
22206 {
22207 Player::HUD_t::FollowerDisplay_t::numInfiniteSplitscreenCompactBars = ally_itr->value["splitscreen_max_compact_bars"].GetInt();
22208 }
22209 if ( ally_itr->value.HasMember("max_fullsize_name_len") )
22210 {
22211 AllyStatusBarSettings_t::FollowerBars_t::entrySettings.maxNameLengthFullsize = ally_itr->value["max_fullsize_name_len"].GetInt();
22212 }
22213 if ( ally_itr->value.HasMember("max_compact_name_len") )
22214 {
22215 AllyStatusBarSettings_t::FollowerBars_t::entrySettings.maxNameLengthCompact = ally_itr->value["max_compact_name_len"].GetInt();
22216 }
22217 if ( ally_itr->value.HasMember("max_splitscreen_fullsize_name_len") )
22218 {
22219 AllyStatusBarSettings_t::FollowerBars_t::entrySettings.maxNameLengthSplitscreenFullsize = ally_itr->value["max_splitscreen_fullsize_name_len"].GetInt();
22220 }
22221 if ( ally_itr->value.HasMember("max_splitscreen_compact_name_len") )
22222 {
22223 AllyStatusBarSettings_t::FollowerBars_t::entrySettings.maxNameLengthSplitscreenCompact = ally_itr->value["max_splitscreen_compact_name_len"].GetInt();
22224 }
22225 if ( ally_itr->value.HasMember("base_y_pos") )
22226 {
22227 AllyStatusBarSettings_t::FollowerBars_t::entrySettings.baseY = ally_itr->value["base_y_pos"].GetInt();
22228 }
22229 if ( ally_itr->value.HasMember("base_y_pos_splitscreen") )
22230 {
22231 AllyStatusBarSettings_t::FollowerBars_t::entrySettings.baseYSplitscreen = ally_itr->value["base_y_pos_splitscreen"].GetInt();
22232 }
22233 if ( ally_itr->value.HasMember("hp") )
22234 {
22235 for ( auto val_itr = ally_itr->value["hp"].MemberBegin();
22236 val_itr != ally_itr->value["hp"].MemberEnd(); ++val_itr )
22237 {
22238 std::string key = val_itr->name.GetString();
22239 auto& bar = AllyStatusBarSettings_t::FollowerBars_t::hpBar;
22240 if ( key == "bar_pixel_width" )
22241 {
22242 bar.barPixelWidth = val_itr->value.GetInt();
22243 }
22244 else if ( key == "bar_compact_pixel_width" )
22245 {
22246 bar.barCompactPixelWidth = val_itr->value.GetInt();
22247 }
22248 else if ( key == "bar_splitscreen_pixel_width_offset" )
22249 {
22250 bar.barSplitscreenPixelWidthOffset = val_itr->value.GetInt();
22251 }
22252 else if ( key == "bar_max_amount_threshold" )
22253 {
22254 bar.barMaxWidthAmount = val_itr->value.GetInt();
22255 }
22256 else if ( key == "bar_interval_to_increase_width" )
22257 {
22258 bar.barIntervalToIncreaseWidth = val_itr->value.GetInt();
22259 }
22260 else if ( key == "bar_interval_start_value" )
22261 {
22262 bar.barIntervalStartValue = val_itr->value.GetInt();
22263 }
22264 else if ( key == "bar_width_increase_percent_on_interval" )
22265 {
22266 bar.barWidthIncreasePercentOnInterval = val_itr->value.GetDouble();
22267 }
22268 else if ( key == "bar_base_percent" )
22269 {
22270 bar.barBasePercentSize = val_itr->value.GetInt();
22271 }
22272 else if ( key == "bar_compact_max_amount_threshold" )
22273 {
22274 bar.barCompactMaxWidthAmount = val_itr->value.GetInt();
22275 }
22276 else if ( key == "bar_compact_interval_to_increase_width" )
22277 {
22278 bar.barCompactIntervalToIncreaseWidth = val_itr->value.GetInt();
22279 }
22280 else if ( key == "bar_compact_interval_start_value" )
22281 {
22282 bar.barCompactIntervalStartValue = val_itr->value.GetInt();
22283 }
22284 else if ( key == "bar_compact_width_increase_percent_on_interval" )
22285 {
22286 bar.barCompactWidthIncreasePercentOnInterval = val_itr->value.GetDouble();
22287 }
22288 else if ( key == "bar_compact_base_percent" )
22289 {
22290 bar.barCompactBasePercentSize = val_itr->value.GetInt();
22291 }
22292 }
22293 }
22294 if ( ally_itr->value.HasMember("mp") )
22295 {
22296 for ( auto val_itr = ally_itr->value["mp"].MemberBegin();
22297 val_itr != ally_itr->value["mp"].MemberEnd(); ++val_itr )
22298 {
22299 std::string key = val_itr->name.GetString();
22300 auto& bar = AllyStatusBarSettings_t::FollowerBars_t::mpBar;
22301 if ( key == "bar_pixel_width" )
22302 {
22303 bar.barPixelWidth = val_itr->value.GetInt();
22304 }
22305 else if ( key == "bar_compact_pixel_width" )
22306 {
22307 bar.barCompactPixelWidth = val_itr->value.GetInt();
22308 }
22309 else if ( key == "bar_max_amount_threshold" )
22310 {
22311 bar.barMaxWidthAmount = val_itr->value.GetInt();
22312 }
22313 else if ( key == "bar_interval_to_increase_width" )
22314 {
22315 bar.barIntervalToIncreaseWidth = val_itr->value.GetInt();
22316 }
22317 else if ( key == "bar_interval_start_value" )
22318 {
22319 bar.barIntervalStartValue = val_itr->value.GetInt();
22320 }
22321 else if ( key == "bar_width_increase_percent_on_interval" )
22322 {
22323 bar.barWidthIncreasePercentOnInterval = val_itr->value.GetDouble();
22324 }
22325 else if ( key == "bar_base_percent" )
22326 {
22327 bar.barBasePercentSize = val_itr->value.GetInt();
22328 }
22329 else if ( key == "bar_compact_max_amount_threshold" )
22330 {
22331 bar.barCompactMaxWidthAmount = val_itr->value.GetInt();
22332 }
22333 else if ( key == "bar_compact_interval_to_increase_width" )
22334 {
22335 bar.barCompactIntervalToIncreaseWidth = val_itr->value.GetInt();
22336 }
22337 else if ( key == "bar_compact_interval_start_value" )
22338 {
22339 bar.barCompactIntervalStartValue = val_itr->value.GetInt();
22340 }
22341 else if ( key == "bar_compact_width_increase_percent_on_interval" )
22342 {
22343 bar.barCompactWidthIncreasePercentOnInterval = val_itr->value.GetDouble();
22344 }
22345 else if ( key == "bar_compact_base_percent" )
22346 {
22347 bar.barCompactBasePercentSize = val_itr->value.GetInt();
22348 }
22349 }
22350 }
22351 }
22352 else if ( type == "players" )
22353 {
22354 if ( ally_itr->value.HasMember("entry_height") )
22355 {
22356 AllyStatusBarSettings_t::PlayerBars_t::entrySettings.entryHeight = ally_itr->value["entry_height"].GetInt();
22357 }
22358 if ( ally_itr->value.HasMember("entry_compact_height") )
22359 {
22360 AllyStatusBarSettings_t::PlayerBars_t::entrySettings.entryCompactHeight = ally_itr->value["entry_compact_height"].GetInt();
22361 }
22362 if ( ally_itr->value.HasMember("max_fullsize_name_len") )
22363 {
22364 AllyStatusBarSettings_t::PlayerBars_t::entrySettings.maxNameLengthFullsize = ally_itr->value["max_fullsize_name_len"].GetInt();
22365 }
22366 if ( ally_itr->value.HasMember("max_compact_name_len") )
22367 {
22368 AllyStatusBarSettings_t::PlayerBars_t::entrySettings.maxNameLengthCompact = ally_itr->value["max_compact_name_len"].GetInt();
22369 }
22370 if ( ally_itr->value.HasMember("max_splitscreen_fullsize_name_len") )
22371 {
22372 AllyStatusBarSettings_t::PlayerBars_t::entrySettings.maxNameLengthSplitscreenFullsize = ally_itr->value["max_splitscreen_fullsize_name_len"].GetInt();
22373 }
22374 if ( ally_itr->value.HasMember("max_splitscreen_compact_name_len") )
22375 {
22376 AllyStatusBarSettings_t::PlayerBars_t::entrySettings.maxNameLengthSplitscreenCompact = ally_itr->value["max_splitscreen_compact_name_len"].GetInt();
22377 }
22378 if ( ally_itr->value.HasMember("base_y_pos") )
22379 {
22380 AllyStatusBarSettings_t::PlayerBars_t::entrySettings.baseY = ally_itr->value["base_y_pos"].GetInt();
22381 }
22382 if ( ally_itr->value.HasMember("base_y_pos_splitscreen") )
22383 {
22384 AllyStatusBarSettings_t::PlayerBars_t::entrySettings.baseYSplitscreen = ally_itr->value["base_y_pos_splitscreen"].GetInt();
22385 }
22386 if ( ally_itr->value.HasMember("hp") )
22387 {
22388 for ( auto val_itr = ally_itr->value["hp"].MemberBegin();
22389 val_itr != ally_itr->value["hp"].MemberEnd(); ++val_itr )
22390 {
22391 std::string key = val_itr->name.GetString();
22392 auto& bar = AllyStatusBarSettings_t::PlayerBars_t::hpBar;
22393 if ( key == "bar_pixel_width" )
22394 {
22395 bar.barPixelWidth = val_itr->value.GetInt();
22396 }
22397 else if ( key == "bar_compact_pixel_width" )
22398 {
22399 bar.barCompactPixelWidth = val_itr->value.GetInt();
22400 }
22401 else if ( key == "bar_max_amount_threshold" )
22402 {
22403 bar.barMaxWidthAmount = val_itr->value.GetInt();
22404 }
22405 else if ( key == "bar_interval_to_increase_width" )
22406 {
22407 bar.barIntervalToIncreaseWidth = val_itr->value.GetInt();
22408 }
22409 else if ( key == "bar_interval_start_value" )
22410 {
22411 bar.barIntervalStartValue = val_itr->value.GetInt();
22412 }
22413 else if ( key == "bar_width_increase_percent_on_interval" )
22414 {
22415 bar.barWidthIncreasePercentOnInterval = val_itr->value.GetDouble();
22416 }
22417 else if ( key == "bar_base_percent" )
22418 {
22419 bar.barBasePercentSize = val_itr->value.GetInt();
22420 }
22421 else if ( key == "bar_compact_max_amount_threshold" )
22422 {
22423 bar.barCompactMaxWidthAmount = val_itr->value.GetInt();
22424 }
22425 else if ( key == "bar_compact_interval_to_increase_width" )
22426 {
22427 bar.barCompactIntervalToIncreaseWidth = val_itr->value.GetInt();
22428 }
22429 else if ( key == "bar_compact_interval_start_value" )
22430 {
22431 bar.barCompactIntervalStartValue = val_itr->value.GetInt();
22432 }
22433 else if ( key == "bar_compact_width_increase_percent_on_interval" )
22434 {
22435 bar.barCompactWidthIncreasePercentOnInterval = val_itr->value.GetDouble();
22436 }
22437 else if ( key == "bar_compact_base_percent" )
22438 {
22439 bar.barCompactBasePercentSize = val_itr->value.GetInt();
22440 }
22441 }
22442 }
22443 if ( ally_itr->value.HasMember("mp") )
22444 {
22445 for ( auto val_itr = ally_itr->value["mp"].MemberBegin();
22446 val_itr != ally_itr->value["mp"].MemberEnd(); ++val_itr )
22447 {
22448 std::string key = val_itr->name.GetString();
22449 auto& bar = AllyStatusBarSettings_t::PlayerBars_t::mpBar;
22450 if ( key == "bar_pixel_width" )
22451 {
22452 bar.barPixelWidth = val_itr->value.GetInt();
22453 }
22454 else if ( key == "bar_compact_pixel_width" )
22455 {
22456 bar.barCompactPixelWidth = val_itr->value.GetInt();
22457 }
22458 else if ( key == "bar_max_amount_threshold" )
22459 {
22460 bar.barMaxWidthAmount = val_itr->value.GetInt();
22461 }
22462 else if ( key == "bar_interval_to_increase_width" )
22463 {
22464 bar.barIntervalToIncreaseWidth = val_itr->value.GetInt();
22465 }
22466 else if ( key == "bar_interval_start_value" )
22467 {
22468 bar.barIntervalStartValue = val_itr->value.GetInt();
22469 }
22470 else if ( key == "bar_width_increase_percent_on_interval" )
22471 {
22472 bar.barWidthIncreasePercentOnInterval = val_itr->value.GetDouble();
22473 }
22474 else if ( key == "bar_base_percent" )
22475 {
22476 bar.barBasePercentSize = val_itr->value.GetInt();
22477 }
22478 else if ( key == "bar_compact_max_amount_threshold" )
22479 {
22480 bar.barCompactMaxWidthAmount = val_itr->value.GetInt();
22481 }
22482 else if ( key == "bar_compact_interval_to_increase_width" )
22483 {
22484 bar.barCompactIntervalToIncreaseWidth = val_itr->value.GetInt();
22485 }
22486 else if ( key == "bar_compact_interval_start_value" )
22487 {
22488 bar.barCompactIntervalStartValue = val_itr->value.GetInt();
22489 }
22490 else if ( key == "bar_compact_width_increase_percent_on_interval" )
22491 {
22492 bar.barCompactWidthIncreasePercentOnInterval = val_itr->value.GetDouble();
22493 }
22494 else if ( key == "bar_compact_base_percent" )
22495 {
22496 bar.barCompactBasePercentSize = val_itr->value.GetInt();
22497 }
22498 }
22499 }
22500 }
22501 }
22502 if ( d["hpmpbar"].HasMember("hpmpbar_max_amount_threshold") )
22503 {
22504 hpmpbarMaxWidthAmount = d["hpmpbar"]["hpmpbar_max_amount_threshold"].GetInt();
22505 }
22506 if ( d["hpmpbar"].HasMember("hpmpbar_interval_to_increase_width") )
22507 {
22508 hpmpbarIntervalToIncreaseWidth
22509 = d["hpmpbar"]["hpmpbar_interval_to_increase_width"].GetInt();
22510 }
22511 if ( d["hpmpbar"].HasMember("hpmpbar_base_percent") )
22512 {
22513 hpmpbarBasePercentSize = d["hpmpbar"]["hpmpbar_base_percent"].GetInt();
22514 }
22515 if ( d["hpmpbar"].HasMember("hpmpbar_interval_start_value") )
22516 {
22517 hpmpbarIntervalStartValue = d["hpmpbar"]["hpmpbar_interval_start_value"].GetInt();
22518 }
22519 if ( d["hpmpbar"].HasMember("hpmpbar_width_increase_percent_on_interval") )
22520 {
22521 hpmpbarWidthIncreasePercentOnInterval =
22522 d["hpmpbar"]["hpmpbar_width_increase_percent_on_interval"].GetDouble();
22523 }
22524
22525 if ( d["hpmpbar"].HasMember("hpmpbar_compact_max_amount_threshold") )
22526 {
22527 hpmpbarCompactMaxWidthAmount = d["hpmpbar"]["hpmpbar_compact_max_amount_threshold"].GetInt();
22528 }
22529 if ( d["hpmpbar"].HasMember("hpmpbar_compact_interval_to_increase_width") )
22530 {
22531 hpmpbarCompactIntervalToIncreaseWidth =
22532 d["hpmpbar"]["hpmpbar_compact_interval_to_increase_width"].GetInt();
22533 }
22534 if ( d["hpmpbar"].HasMember("hpmpbar_compact_base_percent") )
22535 {
22536 hpmpbarCompactBasePercentSize = d["hpmpbar"]["hpmpbar_compact_base_percent"].GetInt();
22537 }
22538 if ( d["hpmpbar"].HasMember("hpmpbar_compact_interval_start_value") )
22539 {
22540 hpmpbarCompactIntervalStartValue = d["hpmpbar"]["hpmpbar_compact_interval_start_value"].GetInt();
22541 }
22542 if ( d["hpmpbar"].HasMember("hpmpbar_compact_width_increase_percent_on_interval") )
22543 {
22544 hpmpbarCompactWidthIncreasePercentOnInterval =
22545 d["hpmpbar"]["hpmpbar_compact_width_increase_percent_on_interval"].GetDouble();
22546 }
22547 }
22548 if ( d.HasMember("action_prompts") )
22549 {
22550 if ( d["action_prompts"].HasMember("x_offset") )
22551 {
22552 Player::HUD_t::actionPromptOffsetX = d["action_prompts"]["x_offset"].GetInt();
22553 }
22554 if ( d["action_prompts"].HasMember("x_offset_ghost_prompts") )
22555 {
22556 Player::HUD_t::actionPromptOffsetXGhostPrompts = d["action_prompts"]["x_offset_ghost_prompts"].GetInt();
22557 }
22558 if ( d["action_prompts"].HasMember("y_offset") )
22559 {
22560 Player::HUD_t::actionPromptOffsetY = d["action_prompts"]["y_offset"].GetInt();
22561 }
22562 if ( d["action_prompts"].HasMember("icon_size") )
22563 {
22564 Player::HUD_t::actionPromptIconSize = d["action_prompts"]["icon_size"].GetInt();
22565 }
22566 if ( d["action_prompts"].HasMember("icon_backing_size") )
22567 {
22568 Player::HUD_t::actionPromptBackingSize = d["action_prompts"]["icon_backing_size"].GetInt();
22569 }
22570 if ( d["action_prompts"].HasMember("icon_opacity") )
22571 {
22572 Player::HUD_t::actionPromptIconOpacity = d["action_prompts"]["icon_opacity"].GetInt();
22573 }
22574 if ( d["action_prompts"].HasMember("icon_backing_opacity") )
22575 {
22576 Player::HUD_t::actionPromptIconBackingOpacity = d["action_prompts"]["icon_backing_opacity"].GetInt();
22577 }
22578 if ( d["action_prompts"].HasMember("prompt_img_00") )
22579 {
22580 actionPromptBackingIconPath00 = d["action_prompts"]["prompt_img_00"].GetString();
22581 }
22582 if ( d["action_prompts"].HasMember("prompt_img_20") )
22583 {
22584 actionPromptBackingIconPath20 = d["action_prompts"]["prompt_img_20"].GetString();
22585 }
22586 if ( d["action_prompts"].HasMember("prompt_img_60") )
22587 {
22588 actionPromptBackingIconPath60 = d["action_prompts"]["prompt_img_60"].GetString();
22589 }
22590 if ( d["action_prompts"].HasMember("prompt_img_100") )
22591 {
22592 actionPromptBackingIconPath100 = d["action_prompts"]["prompt_img_100"].GetString();
22593 }
22594 }
22595 if ( d.HasMember("world_items") )
22596 {
22597 if ( d["world_items"].HasMember("scale_modifier") )
22598 {
22599 Player::WorldUI_t::WorldTooltipItem_t::WorldItemSettings_t::scaleMod = d["world_items"]["scale_modifier"].GetDouble();
22600 }
22601 if ( d["world_items"].HasMember("tooltip_opacity") )
22602 {
22603 Player::WorldUI_t::WorldTooltipItem_t::WorldItemSettings_t::opacity = d["world_items"]["tooltip_opacity"].GetDouble();
22604 }
22605 }
22606 if ( d.HasMember("minimap") )
22607 {
22608 if ( d["minimap"].HasMember("minimap_full_default_size") )
22609 {
22610 Player::Minimap_t::fullSize = d["minimap"]["minimap_full_default_size"].GetInt();
22611 }
22612 if ( d["minimap"].HasMember("minimap_compact_default_size") )
22613 {
22614 Player::Minimap_t::compactSize = d["minimap"]["minimap_compact_default_size"].GetInt();
22615 }
22616 if ( d["minimap"].HasMember("minimap_compact_2p_vertical_size") )
22617 {
22618 Player::Minimap_t::compact2pVerticalSize = d["minimap"]["minimap_compact_2p_vertical_size"].GetInt();
22619 }
22620
22621 if ( d["minimap"].HasMember("minimap_full_big_scale") )
22622 {
22623 Player::Minimap_t::fullBigScale = d["minimap"]["minimap_full_big_scale"].GetDouble();
22624 }
22625 if ( d["minimap"].HasMember("minimap_compact_big_scale") )
22626 {
22627 Player::Minimap_t::compactBigScale = d["minimap"]["minimap_compact_big_scale"].GetDouble();
22628 }
22629 if ( d["minimap"].HasMember("minimap_compact_2p_vertical_big_scale") )
22630 {
22631 Player::Minimap_t::compact2pVerticalBigScale = d["minimap"]["minimap_compact_2p_vertical_big_scale"].GetDouble();
22632 }
22633 }
22634 if ( d.HasMember("levelup_anim_curve") )
22635 {
22636 LevelUpAnimBreakpoints.clear();
22637 for ( auto it = d["levelup_anim_curve"].Begin(); it != d["levelup_anim_curve"].End(); ++it )
22638 {
22639 LevelUpAnimBreakpoints.push_back(it->GetInt());
22640 }
22641 }
22642 if ( d.HasMember("dmg_number_anim_curve") )
22643 {
22644 EnemyHPDamageBarHandler::damageGibAnimCurves.clear();
22645 for ( auto it = d["dmg_number_anim_curve"].MemberBegin(); it != d["dmg_number_anim_curve"].MemberEnd(); ++it )
22646 {
22647 std::string name = it->name.GetString();
22648 if ( name == "default" )
22649 {
22650 for ( auto it2 = it->value.Begin(); it2 != it->value.End(); ++it2 )
22651 {
22652 EnemyHPDamageBarHandler::damageGibAnimCurves[DMG_DEFAULT].push_back(it2->GetInt());
22653 }
22654 }
22655 }
22656 }
22657 if ( d.HasMember("damage_indicators") )
22658 {
22659 damageIndicatorSettings.settings.clear();
22660 if ( d["damage_indicators"].HasMember("delete_after_ticks") )
22661 {
22662 damageIndicatorSettings.deleteAfterTicks = d["damage_indicators"]["delete_after_ticks"].GetUint();
22663 }
22664 if ( d["damage_indicators"].HasMember("fade_after_ticks") )
22665 {
22666 damageIndicatorSettings.fadeAfterTicks = d["damage_indicators"]["fade_after_ticks"].GetUint();
22667 }
22668 if ( d["damage_indicators"].HasMember("fade_speed") )
22669 {
22670 damageIndicatorSettings.fadeSpeed = d["damage_indicators"]["fade_speed"].GetDouble();
22671 }
22672 if ( d["damage_indicators"].HasMember("animation_on_damage") )
22673 {
22674 int index = 0;
22675 for ( auto it = d["damage_indicators"]["animation_on_damage"].Begin(); it != d["damage_indicators"]["animation_on_damage"].End(); ++it )
22676 {
22677 damageIndicatorSettings.indicatorDamageFramePaths[index] = it->GetString();
22678 ++index;
22679 if ( index >= 4 )
22680 {
22681 break;
22682 }
22683 }
22684 }
22685 if ( d["damage_indicators"].HasMember("animation_on_blocking") )
22686 {
22687 int index = 0;
22688 for ( auto it = d["damage_indicators"]["animation_on_blocking"].Begin(); it != d["damage_indicators"]["animation_on_blocking"].End(); ++it )
22689 {
22690 damageIndicatorSettings.indicatorBlockedFramePaths[index] = it->GetString();
22691 ++index;
22692 if ( index >= 4 )
22693 {
22694 break;
22695 }
22696 }
22697 }
22698 if ( d["damage_indicators"].HasMember("layout_default") )
22699 {
22700 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_DEFAULT] =
22701 DamageIndicatorSettings_t::Layout_t();
22702 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_DEFAULT].image_size
22703 = d["damage_indicators"]["layout_default"]["image_size"].GetInt();
22704 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_DEFAULT].radius_x
22705 = d["damage_indicators"]["layout_default"]["radius_x"].GetInt();
22706 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_DEFAULT].radius_y
22707 = d["damage_indicators"]["layout_default"]["radius_y"].GetInt();
22708 }
22709 if ( d["damage_indicators"].HasMember("layout_2p_tall") )
22710 {
22711 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_2P_TALL] =
22712 DamageIndicatorSettings_t::Layout_t();
22713 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_2P_TALL].image_size
22714 = d["damage_indicators"]["layout_2p_tall"]["image_size"].GetInt();
22715 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_2P_TALL].radius_x
22716 = d["damage_indicators"]["layout_2p_tall"]["radius_x"].GetInt();
22717 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_2P_TALL].radius_y
22718 = d["damage_indicators"]["layout_2p_tall"]["radius_y"].GetInt();
22719 }
22720 if ( d["damage_indicators"].HasMember("layout_2p_wide") )
22721 {
22722 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_2P_WIDE] =
22723 DamageIndicatorSettings_t::Layout_t();
22724 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_2P_WIDE].image_size
22725 = d["damage_indicators"]["layout_2p_wide"]["image_size"].GetInt();
22726 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_2P_WIDE].radius_x
22727 = d["damage_indicators"]["layout_2p_wide"]["radius_x"].GetInt();
22728 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_2P_WIDE].radius_y
22729 = d["damage_indicators"]["layout_2p_wide"]["radius_y"].GetInt();
22730 }
22731 if ( d["damage_indicators"].HasMember("layout_4p") )
22732 {
22733 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_4P] =
22734 DamageIndicatorSettings_t::Layout_t();
22735 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_4P].image_size
22736 = d["damage_indicators"]["layout_4p"]["image_size"].GetInt();
22737 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_4P].radius_x
22738 = d["damage_indicators"]["layout_4p"]["radius_x"].GetInt();
22739 damageIndicatorSettings.settings[DamageIndicatorSettings_t::LAYOUT_4P].radius_y
22740 = d["damage_indicators"]["layout_4p"]["radius_y"].GetInt();
22741 }
22742 }
22743 if ( d.HasMember("messages") )
22744 {
22745 messageZoneSettings.settings.clear();
22746 std::vector<std::pair<const char*, MessageZoneSettings_t::HotbarTypes_t>> hotbarTypeStrings = {
22747 {"classic_hotbar", MessageZoneSettings_t::HOTBAR_CLASSIC },
22748 {"modern_hotbar_keyboard", MessageZoneSettings_t::HOTBAR_MODERN_KEYBOARD },
22749 {"modern_hotbar_gamepad", MessageZoneSettings_t::HOTBAR_MODERN_GAMEPAD }
22750 };
22751
22752 for ( auto pair : hotbarTypeStrings )
22753 {
22754 if ( d["messages"].HasMember(pair.first) )
22755 {
22756 for ( rapidjson::Value::ConstMemberIterator itr = d["messages"][pair.first].MemberBegin();
22757 itr != d["messages"][pair.first].MemberEnd(); ++itr )
22758 {
22759 std::string alignmentStr = itr->name.GetString();
22760 auto alignment = Player::MessageZone_t::ALIGN_LEFT_BOTTOM;
22761 if ( alignmentStr == "left_top" )
22762 {
22763 alignment = Player::MessageZone_t::ALIGN_LEFT_TOP;
22764 }
22765 else if ( alignmentStr == "left_bottom" )
22766 {
22767 alignment = Player::MessageZone_t::ALIGN_LEFT_BOTTOM;
22768 }
22769 else if ( alignmentStr == "center_bottom" )
22770 {
22771 alignment = Player::MessageZone_t::ALIGN_CENTER_BOTTOM;
22772 }
22773 else
22774 {
22775 continue;
22776 }
22777
22778 auto& setting = messageZoneSettings.addSetting(pair.second, alignment);
22779 for ( rapidjson::Value::ConstMemberIterator itr2 = itr->value.MemberBegin();
22780 itr2 != itr->value.MemberEnd(); ++itr2 )
22781 {
22782 std::string layoutStr = itr2->name.GetString();
22783 auto layout = MessageZoneSettings_t::MessageSettings_t::LAYOUT_DEFAULT;
22784 if ( layoutStr == "default" )
22785 {
22786 layout = MessageZoneSettings_t::MessageSettings_t::LAYOUT_DEFAULT;
22787 }
22788 else if ( layoutStr == "compact_height" )
22789 {
22790 layout = MessageZoneSettings_t::MessageSettings_t::LAYOUT_COMPACT;
22791 }
22792 else
22793 {
22794 continue;
22795 }
22796 setting.layouts[layout].layoutType = layout;
22797 setting.layouts[layout].offsetY = itr2->value["y_offset"].GetInt();
22798 setting.layouts[layout].maxMessages = itr2->value["max_messages"].GetInt();
22799 }
22800 }
22801 }
22802 }
22803 }
22804 if ( d.HasMember("world_dialogue") )
22805 {
22806 if ( d["world_dialogue"].HasMember("types") )
22807 {
22808 for ( rapidjson::Value::ConstMemberIterator itr = d["world_dialogue"]["types"].MemberBegin();
22809 itr != d["world_dialogue"]["types"].MemberEnd(); ++itr )
22810 {
22811 std::string type = itr->name.GetString();
22812 auto dialogueType = Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NONE;
22813 if ( type == "default" )
22814 {
22815 dialogueType = Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NONE;
22816 }
22817 else if ( type == "grave" )
22818 {
22819 dialogueType = Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_GRAVE;
22820 }
22821 else if ( type == "signpost" )
22822 {
22823 dialogueType = Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_SIGNPOST;
22824 }
22825 else if ( type == "human_npc" )
22826 {
22827 dialogueType = Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_NPC;
22828 }
22829 else if ( type == "follower_cmd" )
22830 {
22831 dialogueType = Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_FOLLOWER_CMD;
22832 }
22833 else if ( type == "broadcast" )
22834 {
22835 dialogueType = Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_BROADCAST;
22836 }
22837 else if ( type == "attack" )
22838 {
22839 dialogueType = Player::WorldUI_t::WorldTooltipDialogue_t::DIALOGUE_ATTACK;
22840 }
22841 else
22842 {
22843 continue;
22844 }
22845
22846 auto& settings = Player::WorldUI_t::WorldTooltipDialogue_t::WorldDialogueSettings_t::settings[dialogueType];
22847 if ( itr->value.HasMember("z_offset") )
22848 {
22849 settings.offsetZ = itr->value["z_offset"].GetDouble();
22850 }
22851 if ( itr->value.HasMember("text_delay") )
22852 {
22853 settings.textDelay = itr->value["text_delay"].GetInt();
22854 }
22855 if ( itr->value.HasMember("follow_entity") )
22856 {
22857 settings.followEntity = itr->value["follow_entity"].GetBool();
22858 }
22859 if ( itr->value.HasMember("fade_dist") )
22860 {
22861 settings.fadeDist = itr->value["fade_dist"].GetDouble();
22862 }
22863 if ( itr->value.HasMember("base_ticks_to_display") )
22864 {
22865 settings.baseTicksToDisplay = itr->value["base_ticks_to_display"].GetUint();
22866 }
22867 if ( itr->value.HasMember("extra_ticks_per_line") )
22868 {
22869 settings.extraTicksPerLine = itr->value["extra_ticks_per_line"].GetUint();
22870 }
22871 if ( itr->value.HasMember("max_width") )
22872 {
22873 settings.maxWidth = itr->value["max_width"].GetInt();
22874 }
22875 if ( itr->value.HasMember("padx") )
22876 {
22877 settings.padx = itr->value["padx"].GetInt();
22878 }
22879 if ( itr->value.HasMember("pady") )
22880 {
22881 settings.pady = itr->value["pady"].GetInt();
22882 }
22883 if ( itr->value.HasMember("pad_after_first_line") )
22884 {
22885 settings.padAfterFirstLine = itr->value["pad_after_first_line"].GetInt();
22886 }
22887 if ( itr->value.HasMember("scale_modifier") )
22888 {
22889 settings.scaleMod = itr->value["scale_modifier"].GetDouble();
22890 }
22891 }
22892 }
22893 }
22894 if ( d.HasMember("enemy_hp_bars") )
22895 {
22896 if ( d["enemy_hp_bars"].HasMember("world_height_offsets") )
22897 {
22898 enemyBarSettings.heightOffsets.clear();
22899 for ( rapidjson::Value::ConstMemberIterator itr = d["enemy_hp_bars"]["world_height_offsets"].MemberBegin();
22900 itr != d["enemy_hp_bars"]["world_height_offsets"].MemberEnd(); ++itr )
22901 {
22902 enemyBarSettings.heightOffsets[itr->name.GetString()] = itr->value.GetFloat();
22903 }
22904 }
22905 if ( d["enemy_hp_bars"].HasMember("screen_depth_distance_offset") )
22906 {
22907 enemyBarSettings.screenDistanceOffsets.clear();
22908 for ( rapidjson::Value::ConstMemberIterator itr = d["enemy_hp_bars"]["screen_depth_distance_offset"].MemberBegin();
22909 itr != d["enemy_hp_bars"]["screen_depth_distance_offset"].MemberEnd(); ++itr )
22910 {
22911 enemyBarSettings.screenDistanceOffsets[itr->name.GetString()] = itr->value.GetFloat();
22912 }
22913 }
22914 if ( d["enemy_hp_bars"].HasMember("monster_bar_width_to_hp_intervals") )
22915 {
22916 EnemyHPDamageBarHandler::widthHealthBreakpointsMonsters.clear();
22917 for ( rapidjson::Value::ConstValueIterator itr = d["enemy_hp_bars"]["monster_bar_width_to_hp_intervals"].Begin();
22918 itr != d["enemy_hp_bars"]["monster_bar_width_to_hp_intervals"].End(); ++itr )
22919 {
22920 // you need to FindMember() if getting objects from an array...
22921 auto widthPercentMember = itr->FindMember("width_percent");
22922 auto hpThresholdMember = itr->FindMember("hp_threshold");
22923 if ( !widthPercentMember->value.IsInt() || !hpThresholdMember->value.IsInt() )
22924 {
22925 printlog("[JSON]: Error: Enemy bar HP or width was not int!");
22926 continue;
22927 }
22928 real_t widthPercent = widthPercentMember->value.GetInt();
22929 widthPercent /= 100.0;
22930 int hpThreshold = hpThresholdMember->value.GetInt();
22931 EnemyHPDamageBarHandler::widthHealthBreakpointsMonsters.push_back(
22932 std::make_pair(widthPercent, hpThreshold));
22933 }
22934 }
22935 if ( d["enemy_hp_bars"].HasMember("furniture_bar_width_to_hp_intervals") )
22936 {
22937 EnemyHPDamageBarHandler::widthHealthBreakpointsFurniture.clear();
22938 for ( rapidjson::Value::ConstValueIterator itr = d["enemy_hp_bars"]["furniture_bar_width_to_hp_intervals"].Begin();
22939 itr != d["enemy_hp_bars"]["furniture_bar_width_to_hp_intervals"].End(); ++itr )
22940 {
22941 // you need to FindMember() if getting objects from an array...
22942 auto widthPercentMember = itr->FindMember("width_percent");
22943 auto hpThresholdMember = itr->FindMember("hp_threshold");
22944 if ( !widthPercentMember->value.IsInt() || !hpThresholdMember->value.IsInt() )
22945 {
22946 printlog("[JSON]: Error: Enemy bar HP or width was not int!");
22947 continue;
22948 }
22949 real_t widthPercent = widthPercentMember->value.GetInt();
22950 widthPercent /= 100.0;
22951 int hpThreshold = hpThresholdMember->value.GetInt();
22952 EnemyHPDamageBarHandler::widthHealthBreakpointsFurniture.push_back(
22953 std::make_pair(widthPercent, hpThreshold));
22954 }
22955 }
22956 if ( d["enemy_hp_bars"].HasMember("monster_bar_lifetime_ticks") )
22957 {
22958 EnemyHPDamageBarHandler::maxTickLifetime = d["enemy_hp_bars"]["monster_bar_lifetime_ticks"].GetInt();
22959 }
22960 if ( d["enemy_hp_bars"].HasMember("furniture_bar_lifetime_ticks") )
22961 {
22962 EnemyHPDamageBarHandler::maxTickFurnitureLifetime = d["enemy_hp_bars"]["furniture_bar_lifetime_ticks"].GetInt();
22963 }
22964 if ( d["enemy_hp_bars"].HasMember("quick_fade_delay_ticks") )
22965 {
22966 EnemyHPDamageBarHandler::shortDistanceHPBarFadeTicks = d["enemy_hp_bars"]["quick_fade_delay_ticks"].GetInt();
22967 }
22968 if ( d["enemy_hp_bars"].HasMember("quick_fade_distance_from_player_multiplier") )
22969 {
22970 if ( d["enemy_hp_bars"]["quick_fade_distance_from_player_multiplier"].IsDouble() )
22971 {
22972 EnemyHPDamageBarHandler::shortDistanceHPBarFadeDistance = d["enemy_hp_bars"]["quick_fade_distance_from_player_multiplier"].GetDouble();
22973 }
22974 else
22975 {
22976 EnemyHPDamageBarHandler::shortDistanceHPBarFadeDistance = d["enemy_hp_bars"]["quick_fade_distance_from_player_multiplier"].GetInt();
22977 }
22978 }
22979 }
22980 if ( d.HasMember("colors") )
22981 {
22982 if ( d["colors"].HasMember("itemmenu_heading_text") )
22983 {
22984 hudColors.itemContextMenuHeadingText = makeColor(
22985 d["colors"]["itemmenu_heading_text"]["r"].GetInt(),
22986 d["colors"]["itemmenu_heading_text"]["g"].GetInt(),
22987 d["colors"]["itemmenu_heading_text"]["b"].GetInt(),
22988 d["colors"]["itemmenu_heading_text"]["a"].GetInt());
22989 }
22990 if ( d["colors"].HasMember("itemmenu_option_text") )
22991 {
22992 hudColors.itemContextMenuOptionText = makeColor(
22993 d["colors"]["itemmenu_option_text"]["r"].GetInt(),
22994 d["colors"]["itemmenu_option_text"]["g"].GetInt(),
22995 d["colors"]["itemmenu_option_text"]["b"].GetInt(),
22996 d["colors"]["itemmenu_option_text"]["a"].GetInt());
22997 }
22998 if ( d["colors"].HasMember("itemmenu_selected_text") )
22999 {
23000 hudColors.itemContextMenuOptionSelectedText = makeColor(
23001 d["colors"]["itemmenu_selected_text"]["r"].GetInt(),
23002 d["colors"]["itemmenu_selected_text"]["g"].GetInt(),
23003 d["colors"]["itemmenu_selected_text"]["b"].GetInt(),
23004 d["colors"]["itemmenu_selected_text"]["a"].GetInt());
23005 }
23006 if ( d["colors"].HasMember("itemmenu_selected_img") )
23007 {
23008 hudColors.itemContextMenuOptionSelectedImg = makeColor(
23009 d["colors"]["itemmenu_selected_img"]["r"].GetInt(),
23010 d["colors"]["itemmenu_selected_img"]["g"].GetInt(),
23011 d["colors"]["itemmenu_selected_img"]["b"].GetInt(),
23012 d["colors"]["itemmenu_selected_img"]["a"].GetInt());
23013 }
23014 if ( d["colors"].HasMember("itemmenu_option_img") )
23015 {
23016 hudColors.itemContextMenuOptionImg = makeColor(
23017 d["colors"]["itemmenu_option_img"]["r"].GetInt(),
23018 d["colors"]["itemmenu_option_img"]["g"].GetInt(),
23019 d["colors"]["itemmenu_option_img"]["b"].GetInt(),
23020 d["colors"]["itemmenu_option_img"]["a"].GetInt());
23021 }
23022 if ( d["colors"].HasMember("charsheet_neutral_text") )
23023 {
23024 hudColors.characterSheetNeutral = makeColor(
23025 d["colors"]["charsheet_neutral_text"]["r"].GetInt(),
23026 d["colors"]["charsheet_neutral_text"]["g"].GetInt(),
23027 d["colors"]["charsheet_neutral_text"]["b"].GetInt(),
23028 d["colors"]["charsheet_neutral_text"]["a"].GetInt());
23029 }
23030 if ( d["colors"].HasMember("charsheet_neutral_light_text") )
23031 {
23032 hudColors.characterSheetLightNeutral = makeColor(
23033 d["colors"]["charsheet_neutral_light_text"]["r"].GetInt(),
23034 d["colors"]["charsheet_neutral_light_text"]["g"].GetInt(),
23035 d["colors"]["charsheet_neutral_light_text"]["b"].GetInt(),
23036 d["colors"]["charsheet_neutral_light_text"]["a"].GetInt());
23037 }
23038 if ( d["colors"].HasMember("charsheet_neutral_lighter1_text") )
23039 {
23040 hudColors.characterSheetLighter1Neutral = makeColor(
23041 d["colors"]["charsheet_neutral_lighter1_text"]["r"].GetInt(),
23042 d["colors"]["charsheet_neutral_lighter1_text"]["g"].GetInt(),
23043 d["colors"]["charsheet_neutral_lighter1_text"]["b"].GetInt(),
23044 d["colors"]["charsheet_neutral_lighter1_text"]["a"].GetInt());
23045 }
23046 if ( d["colors"].HasMember("charsheet_neutral_darker1_text") )
23047 {
23048 hudColors.characterSheetDarker1Neutral = makeColor(
23049 d["colors"]["charsheet_neutral_darker1_text"]["r"].GetInt(),
23050 d["colors"]["charsheet_neutral_darker1_text"]["g"].GetInt(),
23051 d["colors"]["charsheet_neutral_darker1_text"]["b"].GetInt(),
23052 d["colors"]["charsheet_neutral_darker1_text"]["a"].GetInt());
23053 }
23054 if ( d["colors"].HasMember("charsheet_positive_text") )
23055 {
23056 hudColors.characterSheetGreen = makeColor(
23057 d["colors"]["charsheet_positive_text"]["r"].GetInt(),
23058 d["colors"]["charsheet_positive_text"]["g"].GetInt(),
23059 d["colors"]["charsheet_positive_text"]["b"].GetInt(),
23060 d["colors"]["charsheet_positive_text"]["a"].GetInt());
23061 }
23062 if ( d["colors"].HasMember("charsheet_negative_text") )
23063 {
23064 hudColors.characterSheetRed = makeColor(
23065 d["colors"]["charsheet_negative_text"]["r"].GetInt(),
23066 d["colors"]["charsheet_negative_text"]["g"].GetInt(),
23067 d["colors"]["charsheet_negative_text"]["b"].GetInt(),
23068 d["colors"]["charsheet_negative_text"]["a"].GetInt());
23069 }
23070 if ( d["colors"].HasMember("charsheet_faint_text") )
23071 {
23072 hudColors.characterSheetFaintText = makeColor(
23073 d["colors"]["charsheet_faint_text"]["r"].GetInt(),
23074 d["colors"]["charsheet_faint_text"]["g"].GetInt(),
23075 d["colors"]["charsheet_faint_text"]["b"].GetInt(),
23076 d["colors"]["charsheet_faint_text"]["a"].GetInt());
23077 }
23078 if ( d["colors"].HasMember("charsheet_off_white_text") )
23079 {
23080 hudColors.characterSheetOffWhiteText = makeColor(
23081 d["colors"]["charsheet_off_white_text"]["r"].GetInt(),
23082 d["colors"]["charsheet_off_white_text"]["g"].GetInt(),
23083 d["colors"]["charsheet_off_white_text"]["b"].GetInt(),
23084 d["colors"]["charsheet_off_white_text"]["a"].GetInt());
23085 }
23086 if ( d["colors"].HasMember("charsheet_heading_text") )
23087 {
23088 hudColors.characterSheetHeadingText = makeColor(
23089 d["colors"]["charsheet_heading_text"]["r"].GetInt(),
23090 d["colors"]["charsheet_heading_text"]["g"].GetInt(),
23091 d["colors"]["charsheet_heading_text"]["b"].GetInt(),
23092 d["colors"]["charsheet_heading_text"]["a"].GetInt());
23093 }
23094 if ( d["colors"].HasMember("charsheet_highlight_text") )
23095 {
23096 hudColors.characterSheetHighlightText = makeColor(
23097 d["colors"]["charsheet_highlight_text"]["r"].GetInt(),
23098 d["colors"]["charsheet_highlight_text"]["g"].GetInt(),
23099 d["colors"]["charsheet_highlight_text"]["b"].GetInt(),
23100 d["colors"]["charsheet_highlight_text"]["a"].GetInt());
23101 }
23102 if ( d["colors"].HasMember("charsheet_base_class_text") )
23103 {
23104 hudColors.characterBaseClassText = makeColor(
23105 d["colors"]["charsheet_base_class_text"]["r"].GetInt(),
23106 d["colors"]["charsheet_base_class_text"]["g"].GetInt(),
23107 d["colors"]["charsheet_base_class_text"]["b"].GetInt(),
23108 d["colors"]["charsheet_base_class_text"]["a"].GetInt());
23109 }
23110 if ( d["colors"].HasMember("charsheet_dlc1_text") )
23111 {
23112 hudColors.characterDLC1ClassText = makeColor(
23113 d["colors"]["charsheet_dlc1_text"]["r"].GetInt(),
23114 d["colors"]["charsheet_dlc1_text"]["g"].GetInt(),
23115 d["colors"]["charsheet_dlc1_text"]["b"].GetInt(),
23116 d["colors"]["charsheet_dlc1_text"]["a"].GetInt());
23117 }
23118 if ( d["colors"].HasMember("charsheet_dlc2_text") )
23119 {
23120 hudColors.characterDLC2ClassText = makeColor(
23121 d["colors"]["charsheet_dlc2_text"]["r"].GetInt(),
23122 d["colors"]["charsheet_dlc2_text"]["g"].GetInt(),
23123 d["colors"]["charsheet_dlc2_text"]["b"].GetInt(),
23124 d["colors"]["charsheet_dlc2_text"]["a"].GetInt());
23125 }
23126 }
23127 if ( d.HasMember("dropdowns") )
23128 {
23129 Player::GUIDropdown_t::allDropDowns.clear();
23130 for ( rapidjson::Value::ConstMemberIterator itr = d["dropdowns"].MemberBegin();
23131 itr != d["dropdowns"].MemberEnd(); ++itr )
23132 {
23133 auto& dropdown = Player::GUIDropdown_t::allDropDowns[itr->name.GetString()];
23134 dropdown.internalName = itr->name.GetString();
23135 if ( itr->value.HasMember("title") )
23136 {
23137 dropdown.title = itr->value["title"].GetString();
23138 }
23139 else
23140 {
23141 dropdown.title = Language::get(4040); // "interact"
23142 }
23143 if ( itr->value.HasMember("align_right") )
23144 {
23145 dropdown.alignRight = itr->value["align_right"].GetBool();
23146 }
23147 if ( itr->value.HasMember("gui_module") )
23148 {
23149 std::string moduleName = itr->value["gui_module"].GetString();
23150 if ( moduleName == "character_sheet" )
23151 {
23152 dropdown.module = Player::GUI_t::MODULE_CHARACTERSHEET;
23153 }
23154 else if ( moduleName == "inventory" )
23155 {
23156 dropdown.module = Player::GUI_t::MODULE_INVENTORY;
23157 }
23158 else if ( moduleName == "chest" )
23159 {
23160 dropdown.module = Player::GUI_t::MODULE_CHEST;
23161 }
23162 else if ( moduleName == "spells" )
23163 {
23164 dropdown.module = Player::GUI_t::MODULE_SPELLS;
23165 }
23166 else if ( moduleName == "hotbar" )
23167 {
23168 dropdown.module = Player::GUI_t::MODULE_HOTBAR;
23169 }
23170 else
23171 {
23172 dropdown.module = Player::GUI_t::MODULE_NONE;
23173 }
23174 }
23175 if ( itr->value.HasMember("default_option") )
23176 {
23177 dropdown.defaultOption = itr->value["default_option"].GetInt();
23178 }
23179 else
23180 {
23181 dropdown.defaultOption = 0;
23182 }
23183 for ( rapidjson::Value::ConstValueIterator options_itr = itr->value["options"].Begin();
23184 options_itr != itr->value["options"].End(); ++options_itr )
23185 {
23186 std::string text = "";
23187 std::string controller_glyph = "";
23188 std::string keyboard_glyph = "";
23189 std::string action = "no_action";
23190 if ( options_itr->HasMember("text") )
23191 {
23192 text = (*options_itr)["text"].GetString();
23193 }
23194 if ( options_itr->HasMember("controller_glyph") )
23195 {
23196 controller_glyph = (*options_itr)["controller_glyph"].GetString();
23197 }
23198 if ( options_itr->HasMember("keyboard_glyph") )
23199 {
23200 keyboard_glyph = (*options_itr)["keyboard_glyph"].GetString();
23201 }
23202 if ( options_itr->HasMember("action") )
23203 {
23204 action = (*options_itr)["action"].GetString();
23205 }
23206 dropdown.options.push_back(Player::GUIDropdown_t::DropdownOption_t(text, keyboard_glyph, controller_glyph, action));
23207 }
23208 }
23209 }
23210 if ( d.HasMember("ping_status") )
23211 {
23212 PingNetworkStatus_t::bEnabled = d["ping_status"]["enabled"].GetBool();
23213
23214 PingNetworkStatus_t::pingLimitGreen = d["ping_status"]["limit_green"].GetInt();
23215 PingNetworkStatus_t::pingLimitYellow = d["ping_status"]["limit_yellow"].GetInt();
23216 PingNetworkStatus_t::pingLimitOrange = d["ping_status"]["limit_orange"].GetInt();
23217
23218 PingNetworkStatus_t::pingHUDDisplayGreen = d["ping_status"]["show_green_always"].GetBool();
23219 PingNetworkStatus_t::pingHUDDisplayYellow = d["ping_status"]["show_yellow_always"].GetBool();
23220 PingNetworkStatus_t::pingHUDDisplayOrange = d["ping_status"]["show_orange_always"].GetBool();
23221 PingNetworkStatus_t::pingHUDDisplayRed = d["ping_status"]["show_red_always"].GetBool();
23222 PingNetworkStatus_t::pingHUDShowOKBriefly = d["ping_status"]["show_green_yellow_briefly"].GetBool();
23223 PingNetworkStatus_t::pingHUDShowNumericValue = d["ping_status"]["show_numeric_on_hud"].GetBool();
23224 }
23225 printlog("[JSON]: Successfully read json file %s", inputPath.c_str());
23226 }
23227 }
23228 }
23229}
23230
23231const int kSpellListHeight = 294;
23232const int kSpellListGridY = 2;
23233void createPlayerSpellList(const int player)
23234{
23235 if ( !gui )
23236 {
23237 return;
23238 }
23239
23240 if ( players[player]->inventoryUI.spellFrame || !players[player]->inventoryUI.frame )
23241 {
23242 return;
23243 }
23244
23245 Frame* frame = players[player]->inventoryUI.frame->addFrame("player spells");
23246 players[player]->inventoryUI.spellFrame = frame;
23247 frame->setSize(SDL_Rect{ 0,
23248 0,
23249 210,
23250 kSpellListHeight });
23251 frame->setHollow(true);
23252 frame->setBorder(0);
23253 frame->setOwner(player);
23254 frame->setInheritParentFrameOpacity(false);
23255
23256 SDL_Rect basePos{ 0, 0, 210, kSpellListHeight };
23257 const int inventorySlotSize = players[player]->inventoryUI.getSlotSize();
23258
23259 players[player]->inventoryUI.spellSlotFrames.clear();
23260
23261 const int baseSlotOffsetX = 18;
23262 const int baseSlotOffsetY = 36;
23263 const int baseGridOffsetY = kSpellListGridY;
23264
23265 SDL_Rect invSlotsPos{ basePos.x + 4, basePos.y + 4, basePos.w, 242 };
23266 {
23267 int numGrids = (players[player]->inventoryUI.MAX_SPELLS_Y / players[player]->inventoryUI.spellPanel.kNumSpellsToDisplayVertical) + 1;
23268
23269 const auto spellSlotsFrame = frame->addFrame("spell slots");
23270 spellSlotsFrame->setSize(invSlotsPos);
23271 spellSlotsFrame->setActualSize(SDL_Rect{ 0, 0, basePos.w, 242 * numGrids });
23272 spellSlotsFrame->setHollow(true);
23273 spellSlotsFrame->setAllowScrollBinds(false);
23274
23275 auto gridImg = spellSlotsFrame->addImage(SDL_Rect{ baseSlotOffsetX, baseGridOffsetY, 162, 242 * numGrids },
23276 0xFFFFFFFF, "*images/ui/Inventory/HUD_Magic_ScrollGrid.png", "grid img");
23277 gridImg->tiled = true;
23278
23279 SDL_Rect currentSlotPos{ baseSlotOffsetX, baseSlotOffsetY, inventorySlotSize, inventorySlotSize };
23280 const int maxSpellsX = Player::Inventory_t::MAX_SPELLS_X;
23281 const int maxSpellsY = Player::Inventory_t::MAX_SPELLS_Y;
23282
23283 for ( int x = 0; x < maxSpellsX; ++x )
23284 {
23285 currentSlotPos.x = baseSlotOffsetX + (x * inventorySlotSize);
23286 for ( int y = 0; y < maxSpellsY; ++y )
23287 {
23288 currentSlotPos.y = baseSlotOffsetY + (y * inventorySlotSize);
23289
23290 char slotname[32] = "";
23291 snprintf(slotname, sizeof(slotname), "spell %d %d", x, y);
23292
23293 auto slotFrame = spellSlotsFrame->addFrame(slotname);
23294 players[player]->inventoryUI.spellSlotFrames[x + y * 100] = slotFrame;
23295 SDL_Rect slotPos{ currentSlotPos.x, currentSlotPos.y, inventorySlotSize, inventorySlotSize };
23296 slotFrame->setSize(slotPos);
23297
23298 createPlayerInventorySlotFrameElements(slotFrame);
23299 }
23300 }
23301 }
23302
23303 {
23304 auto bgFrame = frame->addFrame("spell base");
23305 bgFrame->setSize(basePos);
23306 bgFrame->setHollow(true);
23307 const auto bgSize = bgFrame->getSize();
23308 auto bg = bgFrame->addImage(SDL_Rect{ 0, 0, 210, kSpellListHeight },
23309 makeColor(255, 255, 255, 255),
23310 "*#images/ui/Inventory/HUD_Magic_Base.png", "spell base img");
23311 playerInventoryFrames[player].spellBaseImg = bg;
23312
23313 auto slider = bgFrame->addSlider("spell slider");
23314 slider->setBorder(16);
23315 slider->setMinValue(0);
23316 slider->setMaxValue(100);
23317 slider->setValue(0);
23318 SDL_Rect sliderPos{ basePos.w - 26, 50, 20, 192 };
23319 slider->setRailSize(sliderPos);
23320 slider->setHandleSize(SDL_Rect{ 0, 0, 20, 28 });
23321 slider->setOrientation(Slider::SLIDER_VERTICAL);
23322 //slider->setCallback(callback);
23323 slider->setColor(makeColor(255, 255, 255, 255));
23324 slider->setHighlightColor(makeColor(255, 255, 255, 255));
23325 slider->setHandleImage("*#images/ui/Sliders/HUD_Magic_Slider_Emerald_01.png");
23326 slider->setRailImage("*#images/ui/Sliders/HUD_Slider_Blank.png");
23327 slider->setHideGlyphs(true);
23328 slider->setHideKeyboardGlyphs(true);
23329 slider->setHideSelectors(true);
23330 slider->setMenuConfirmControlType(0);
23331
23332 auto sliderCapTop = bgFrame->addImage(SDL_Rect{ sliderPos.x + 2, sliderPos.y, 16, 16 },
23333 makeColor(255, 255, 255, 255),
23334 "*#images/ui/Sliders/HUD_Magic_Slider_SettingTop_01.png", "spell slider top");
23335 sliderCapTop->ontop = true;
23336 auto sliderCapBot = bgFrame->addImage(SDL_Rect{ sliderPos.x + 2, sliderPos.y + sliderPos.h - 16, 16, 16 },
23337 makeColor(255, 255, 255, 255),
23338 "*#images/ui/Sliders/HUD_Magic_Slider_SettingBot_01.png", "spell slider bot");
23339 sliderCapBot->ontop = true;
23340
23341 const char* font = "fonts/pixel_maz.ttf#32#2";
23342 auto titleText = bgFrame->addField("title txt", 64);
23343 titleText->setFont(font);
23344 titleText->setText(Language::get(5958));
23345 titleText->setHJustify(Field::justify_t::CENTER);
23346 titleText->setVJustify(Field::justify_t::TOP);
23347 titleText->setSize(SDL_Rect{ 56, 12, 96, 24 });
23348 titleText->setColor(makeColor(236, 175, 28, 255));
23349
23350 auto closeBtn = bgFrame->addButton("close spell button");
23351 SDL_Rect closeBtnPos;
23352 closeBtnPos.x = 180;
23353 closeBtnPos.y = 6;
23354 closeBtnPos.w = 26;
23355 closeBtnPos.h = 26;
23356 closeBtn->setSize(closeBtnPos);
23357 closeBtn->setColor(makeColor(255, 255, 255, 255));
23358 closeBtn->setHighlightColor(makeColor(255, 255, 255, 255));
23359 closeBtn->setTextHighlightColor(makeColor(201, 162, 100, 255));
23360 closeBtn->setText("X");
23361 closeBtn->setFont(font);
23362 closeBtn->setHideGlyphs(true);
23363 closeBtn->setHideKeyboardGlyphs(true);
23364 closeBtn->setHideSelectors(true);
23365 closeBtn->setMenuConfirmControlType(0);
23366 closeBtn->setBackground("*#images/ui/Inventory/chests/Button_X_00.png");
23367 closeBtn->setBackgroundHighlighted("*#images/ui/Inventory/chests/Button_XHigh_00.png");
23368 closeBtn->setBackgroundActivated("*#images/ui/Inventory/chests/Button_XPress_00.png");
23369 closeBtn->setCallback([](Button& button) {
23370 if ( players[button.getOwner()]->inventory_mode == INVENTORY_MODE_SPELL )
23371 {
23372 players[button.getOwner()]->inventoryUI.cycleInventoryTab();
23373 }
23374 players[button.getOwner()]->inventoryUI.spellPanel.closeSpellPanel();
23375 });
23376 closeBtn->setTickCallback([](Widget& widget) {
23377 if ( widget.isSelected() )
23378 {
23379 if ( !inputs.getVirtualMouse(widget.getOwner())->draw_cursor )
23380 {
23381 widget.deselect();
23382 }
23383 }
23384 });
23385
23386 auto skillBg = bgFrame->addImage(SDL_Rect{ 6, 6, 32, 32 },
23387 makeColor(255, 255, 255, 255),
23388 "*#images/ui/Inventory/HUD_Magic_Casting_BG_01.png", "spell skill bg");
23389
23390 auto skillIcon = bgFrame->addImage(SDL_Rect{ skillBg->pos.x + 4, skillBg->pos.y + 4, 24, 24 },
23391 makeColor(255, 255, 255, 255),
23392 "", "spell skill icon");
23393
23394 auto closeGlyph = bgFrame->addImage(SDL_Rect{ 0, 0, 24, 24 },
23395 makeColor(255, 255, 255, 255),
23396 "", "close spell glyph");
23397 closeGlyph->disabled = true;
23398 }
23399}
23400
23401void closeChestGUIAction(const int player)
23402{
23403 players[player]->hud.compactLayoutMode = Player::HUD_t::COMPACT_LAYOUT_INVENTORY;
23404 //players[player]->inventory_mode = INVENTORY_MODE_ITEM;
23405 if ( players[player]->GUI.activeModule != Player::GUI_t::MODULE_INVENTORY )
23406 {
23407 players[player]->GUI.activateModule(Player::GUI_t::MODULE_INVENTORY);
23408 if ( !inputs.getVirtualMouse(player)->draw_cursor )
23409 {
23410 players[player]->GUI.warpControllerToModule(false);
23411 }
23412 }
23413 if ( openedChest[player] )
23414 {
23415 openedChest[player]->closeChest();
23416 }
23417 else
23418 {
23419 players[player]->inventoryUI.chestGUI.closeChest();
23420 }
23421}
23422
23423bool takeAllChestGUIAction(const int player)
23424{
23425 if ( !openedChest[player] )
23426 {
23427 return false;
23428 }
23429
23430 list_t* chest_inventory = nullptr;
23431 if ( multiplayer == CLIENT2 )
23432 {
23433 chest_inventory = &chestInv[player];
23434 }
23435 else if ( openedChest[player]->children.first && openedChest[player]->children.first->element )
23436 {
23437 chest_inventory = (list_t*)openedChest[player]->children.first->element;
23438 }
23439 if ( !chest_inventory )
23440 {
23441 // no chest inventory available
23442 return false;
23443 }
23444
23445 std::vector<std::pair<int, Item*>> chestSlotOrder;
23446 for ( node_t* node = chest_inventory->first; node != nullptr; node = node->next )
23447 {
23448 Item* item2 = static_cast<Item*>(node->element);
23449 if ( item2 )
23450 {
23451 int key = item2->x + item2->y * 100;
23452 chestSlotOrder.push_back(std::make_pair(key, item2));
23453 }
23454 }
23455 int numItems = (int)chestSlotOrder.size();
23456 std::sort(chestSlotOrder.begin(), chestSlotOrder.end()); // sort ascending by position, left to right, then down
23457 int pickedUpItems = 0;
23458 for ( auto& keyValue : chestSlotOrder )
23459 {
23460 Item* item = keyValue.second;
23461 bool tryAddToInventory = true;
23462 int loops = 0;
23463 while ( tryAddToInventory )
23464 {
23465 ++loops;
23466 if ( item->count <= 0 )
23467 {
23468 break;
23469 }
23470 int oldItemQty = 0;
23471 int destItemQty = 0;
23472 bool oldIdentify = item->identified;
23473 if ( skillCapstoneUnlocked(player, PRO_APPRAISAL) )
23474 {
23475 item->identified = true;
23476 }
23477 auto result = getItemStackingBehavior(player, item, nullptr, oldItemQty, destItemQty);
23478 item->identified = oldIdentify;
23479 int amountToPlace = item->count - oldItemQty;
23480 assert(amountToPlace > 0)(static_cast<void> (0));
23481 if ( amountToPlace <= 0 )
23482 {
23483 break;
23484 }
23485 switch ( result.resultType )
23486 {
23487 case ITEM_ADDED_PARTIALLY_TO_DESTINATION_STACK:
23488 {
23489 if ( Item* inventoryItem = takeItemFromChest(player, item, amountToPlace, result.itemToStackInto, false, false) )
23490 {
23491 // need to do another place operation.
23492 }
23493 else
23494 {
23495 tryAddToInventory = false;
23496 }
23497 if ( loops == 1 )
23498 {
23499 ++pickedUpItems;
23500 }
23501 break;
23502 }
23503 case ITEM_ADDED_ENTIRELY_TO_DESTINATION_STACK:
23504 {
23505 // operation success, can finish here.
23506 Item* inventoryItem = takeItemFromChest(player, item, amountToPlace, result.itemToStackInto, false, false);
23507 tryAddToInventory = false;
23508 if ( loops == 1 )
23509 {
23510 ++pickedUpItems;
23511 }
23512 break;
23513 }
23514 case ITEM_ADDED_WITHOUT_NEEDING_STACK:
23515 {
23516 // check for inventory space
23517 if ( !players[player]->inventoryUI.bItemInventoryHasFreeSlot() )
23518 {
23519 // no space
23520 tryAddToInventory = false;
23521 break;
23522 }
23523 Item* inventoryItem = takeItemFromChest(player, item, amountToPlace, nullptr, true, false);
23524 if ( oldItemQty > 0 )
23525 {
23526 // more work to do (unusually large stacks exceeding normal limits)
23527 }
23528 else
23529 {
23530 tryAddToInventory = false;
23531 }
23532 if ( loops == 1 )
23533 {
23534 ++pickedUpItems;
23535 }
23536 break;
23537 }
23538 default:
23539 // error out
23540 tryAddToInventory = false;
23541 break;
23542 }
23543 }
23544 }
23545
23546 if ( pickedUpItems > 0 )
23547 {
23548 messagePlayer(player, MESSAGE_INVENTORY, Language::get(4099), pickedUpItems);
23549 playSound(35 + local_rng.rand() % 3, 64);
23550 }
23551 else if ( pickedUpItems == 0 && numItems > 0 )
23552 {
23553 messagePlayer(player, MESSAGE_INVENTORY, Language::get(4100));
23554 playSoundPlayer(player, 90, 64);
23555 }
23556
23557 return true;
23558}
23559
23560const int chestBaseImgBorderWidth = 16;
23561const int chestBaseImgBorderTopHeight = 20;
23562PlayerInventoryFrames_t playerInventoryFrames[MAXPLAYERS4];
23563
23564void createChestGUI(const int player)
23565{
23566 if ( !gui )
23567 {
23568 return;
23569 }
23570
23571 if ( players[player]->inventoryUI.chestFrame || !players[player]->inventoryUI.frame )
23572 {
23573 return;
23574 }
23575
23576 Frame* frame = players[player]->inventoryUI.frame->addFrame("chest");
23577 players[player]->inventoryUI.chestFrame = frame;
23578
23579 frame->setSize(SDL_Rect{ 0,
23580 0,
23581 194,
23582 250 });
23583 frame->setHollow(true);
23584 frame->setBorder(0);
23585 frame->setOwner(player);
23586 frame->setInheritParentFrameOpacity(false);
23587
23588 SDL_Rect basePos{ 0, 0, 194, 130 };
23589 {
23590 auto bgFrame = frame->addFrame("chest base");
23591 playerInventoryFrames[player].chestBgFrame = bgFrame;
23592 bgFrame->setSize(basePos);
23593 bgFrame->setHollow(false);
23594 const auto bgSize = bgFrame->getSize();
23595 auto bg = bgFrame->addImage(SDL_Rect{ 6, 0, 182, 172 },
23596 makeColor( 255, 255, 255, 255),
23597 "*#images/ui/Inventory/chests/Chest_Main_00.png", "chest base img");
23598 playerInventoryFrames[player].chestBaseImg = bg;
23599 auto bg2 = bgFrame->addImage(SDL_Rect{ 0, 0, 194, 66 },
23600 makeColor( 255, 255, 255, 255),
23601 "*#images/ui/Inventory/chests/Chest_Top_00.png", "chest lid img");
23602 //bg->disabled = false;
23603
23604 //auto slider = bgFrame->addSlider("chest slider");
23605 //slider->setBorder(24);
23606 //slider->setMinValue(0);
23607 //slider->setMaxValue(100);
23608 //slider->setValue(0);
23609 //SDL_Rect sliderPos{ basePos.w - 38, 8, 30, 234 };
23610 //slider->setRailSize(sliderPos);
23611 //slider->setHandleSize(SDL_Rect{ 0, 0, 34, 34 });
23612 //slider->setOrientation(Slider::SLIDER_VERTICAL);
23613 ////slider->setCallback(callback);
23614 //slider->setColor(makeColor(255, 255, 255, 255));
23615 //slider->setHighlightColor(makeColor(255, 255, 255, 255));
23616 //slider->setHandleImage("*#images/ui/Main Menus/Settings/Settings_Slider_Boulder00.png");
23617 //slider->setRailImage("*#images/ui/Main Menus/Settings/Settings_Slider_Backing00.png");
23618 //slider->setHideGlyphs(true);
23619 //slider->setHideKeyboardGlyphs(true);
23620 //slider->setHideSelectors(true);
23621 //slider->setMenuConfirmControlType(0);
23622
23623 const char* font = "fonts/pixel_maz.ttf#32#2";
23624 auto titleText = bgFrame->addField("title txt", 64);
23625 titleText->setFont(font);
23626 titleText->setText(Language::get(5959));
23627 titleText->setHJustify(Field::justify_t::CENTER);
23628 titleText->setVJustify(Field::justify_t::CENTER);
23629 titleText->setSize(SDL_Rect{ basePos.x + 4, 0, 162, 32 });
23630 titleText->setColor(makeColor(188, 154, 114, 255));
23631
23632 auto closeBtn = bgFrame->addButton("close chest button");
23633 SDL_Rect closeBtnPos = titleText->getSize();
23634 closeBtnPos.x = closeBtnPos.x + closeBtnPos.w - 98;
23635 closeBtnPos.w = 26;
23636 closeBtnPos.h = 26;
23637 closeBtn->setSize(closeBtnPos);
23638 closeBtn->setColor(makeColor(255, 255, 255, 255));
23639 closeBtn->setHighlightColor(makeColor(255, 255, 255, 255));
23640 closeBtn->setText("X");
23641 closeBtn->setFont(font);
23642 closeBtn->setHideGlyphs(true);
23643 closeBtn->setHideKeyboardGlyphs(true);
23644 closeBtn->setHideSelectors(true);
23645 closeBtn->setMenuConfirmControlType(0);
23646 closeBtn->setBackground("*#images/ui/Inventory/chests/Button_X_00.png");
23647 closeBtn->setBackgroundHighlighted("*#images/ui/Inventory/chests/Button_XHigh_00.png");
23648 closeBtn->setBackgroundActivated("*#images/ui/Inventory/chests/Button_XPress_00.png");
23649 closeBtn->setTextHighlightColor(makeColor(201, 162, 100, 255));
23650 closeBtn->setCallback([](Button& button) {
23651 closeChestGUIAction(button.getOwner());
23652 });
23653
23654 auto grabAllBtn = bgFrame->addButton("grab all button");
23655 SDL_Rect grabBtnPos = titleText->getSize();
23656 grabBtnPos.x = closeBtnPos.x + closeBtnPos.w - 86;
23657 grabBtnPos.w = 86;
23658 grabBtnPos.h = 26;
23659 grabAllBtn->setSize(grabBtnPos);
23660 grabAllBtn->setColor(makeColor(255, 255, 255, 255));
23661 grabAllBtn->setHighlightColor(makeColor(255, 255, 255, 255));
23662 grabAllBtn->setText(Language::get(5960));
23663 grabAllBtn->setFont(font);
23664 grabAllBtn->setHideGlyphs(true);
23665 grabAllBtn->setHideKeyboardGlyphs(true);
23666 grabAllBtn->setHideSelectors(true);
23667 grabAllBtn->setMenuConfirmControlType(0);
23668 grabAllBtn->setBackground("*#images/ui/Inventory/chests/Button_TakeAll_00.png");
23669 grabAllBtn->setBackgroundHighlighted("*#images/ui/Inventory/chests/Button_TakeAllHigh_00.png");
23670 grabAllBtn->setBackgroundActivated("*#images/ui/Inventory/chests/Button_TakeAllPress_00.png");
23671 grabAllBtn->setTextHighlightColor(makeColor(201, 162, 100, 255));
23672 grabAllBtn->setCallback([](Button& button) {
23673 takeAllChestGUIAction(button.getOwner());
23674 Player::soundActivate();
23675 });
23676
23677 std::string promptFont = "fonts/pixel_maz.ttf#32#2";
23678 const int promptWidth = 60;
23679 const int promptHeight = 27;
23680 auto promptBack = bgFrame->addField("prompt back txt", 16);
23681 promptBack->setSize(SDL_Rect{ 0, 0, promptWidth, promptHeight });
23682 promptBack->setFont(promptFont.c_str());
23683 promptBack->setHJustify(Field::justify_t::RIGHT);
23684 promptBack->setVJustify(Field::justify_t::CENTER);
23685 promptBack->setText(Language::get(4053));
23686 //promptBack->setOntop(true);
23687 promptBack->setColor(makeColor(201, 162, 100, 255));
23688
23689 auto promptBackImg = bgFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
23690 "", "prompt back img");
23691 promptBackImg->disabled = true;
23692
23693 auto promptGrabAll = bgFrame->addField("prompt grab txt", 16);
23694 promptGrabAll->setSize(SDL_Rect{ 0, 0, promptWidth, promptHeight });
23695 promptGrabAll->setFont(promptFont.c_str());
23696 promptGrabAll->setHJustify(Field::justify_t::RIGHT);
23697 promptGrabAll->setVJustify(Field::justify_t::CENTER);
23698 promptGrabAll->setText(Language::get(4091));
23699 //promptBack->setOntop(true);
23700 promptGrabAll->setColor(makeColor(201, 162, 100, 255));
23701
23702 auto promptGrabImg = bgFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
23703 "", "prompt grab img");
23704 promptBackImg->disabled = true;
23705 }
23706
23707 const int inventorySlotSize = players[player]->inventoryUI.getSlotSize();
23708
23709 players[player]->inventoryUI.chestSlotFrames.clear();
23710
23711 const int baseSlotOffsetX = 0;
23712 const int baseSlotOffsetY = 0;
23713
23714 const int gridHeight = 120 + 2; // 120px is grid img, plus 2px to tile the image for bottom border.
23715 SDL_Rect invSlotsPos{ basePos.x + chestBaseImgBorderWidth, basePos.y + 4 + chestBaseImgBorderTopHeight, basePos.w, gridHeight };
23716 {
23717 int numGrids = (players[player]->inventoryUI.MAX_CHEST_Y / players[player]->inventoryUI.chestGUI.kNumItemsToDisplayVertical) + 1;
23718
23719 const auto chestSlotsFrame = frame->addFrame("chest slots");
23720 chestSlotsFrame->setSize(invSlotsPos);
23721 chestSlotsFrame->setActualSize(SDL_Rect{ 0, 0, basePos.w, gridHeight * numGrids });
23722 chestSlotsFrame->setHollow(true);
23723 chestSlotsFrame->setAllowScrollBinds(false);
23724 playerInventoryFrames[player].chestFrameSlots = chestSlotsFrame;
23725
23726 auto gridImg = chestSlotsFrame->addImage(SDL_Rect{ baseSlotOffsetX, baseSlotOffsetY, 162, gridHeight * numGrids },
23727 makeColor(255, 255, 255, 32), "*#images/ui/Inventory/HUD_Chest4x3_ScrollGrid.png", "grid img");
23728 gridImg->tiled = true;
23729
23730 SDL_Rect currentSlotPos{ baseSlotOffsetX, baseSlotOffsetY, inventorySlotSize, inventorySlotSize };
23731 const int maxChestX = Player::Inventory_t::MAX_CHEST_X;
23732 const int maxChestY = Player::Inventory_t::MAX_CHEST_Y;
23733
23734 for ( int x = 0; x < maxChestX; ++x )
23735 {
23736 currentSlotPos.x = baseSlotOffsetX + (x * inventorySlotSize);
23737 for ( int y = 0; y < maxChestY; ++y )
23738 {
23739 currentSlotPos.y = baseSlotOffsetY + (y * inventorySlotSize);
23740
23741 char slotname[32] = "";
23742 snprintf(slotname, sizeof(slotname), "chest %d %d", x, y);
23743
23744 auto slotFrame = chestSlotsFrame->addFrame(slotname);
23745 players[player]->inventoryUI.chestSlotFrames[x + y * 100] = slotFrame;
23746 SDL_Rect slotPos{ currentSlotPos.x, currentSlotPos.y, inventorySlotSize, inventorySlotSize };
23747 slotFrame->setSize(slotPos);
23748
23749 createPlayerInventorySlotFrameElements(slotFrame);
23750 }
23751 }
23752 }
23753}
23754
23755const int Player::ShopGUI_t::MAX_SHOP_X = 5;
23756const int Player::ShopGUI_t::MAX_SHOP_Y = 5;
23757
23758void closeShopGUIAction(const int player)
23759{
23760 players[player]->hud.compactLayoutMode = Player::HUD_t::COMPACT_LAYOUT_INVENTORY;
23761 if ( players[player]->GUI.activeModule != Player::GUI_t::MODULE_INVENTORY )
23762 {
23763 players[player]->GUI.activateModule(Player::GUI_t::MODULE_INVENTORY);
23764 if ( !inputs.getVirtualMouse(player)->draw_cursor )
23765 {
23766 players[player]->GUI.warpControllerToModule(false);
23767 }
23768 }
23769 if ( uidToEntity(shopkeeper[player]) )
23770 {
23771 closeShop(player);
23772 }
23773 else
23774 {
23775 players[player]->shopGUI.closeShop();
23776 }
23777}
23778
23779void toggleShopBuybackView(const int player)
23780{
23781 players[player]->shopGUI.buybackView = !players[player]->shopGUI.buybackView;
23782}
23783
23784void createShopGUI(const int player)
23785{
23786 if ( !gui )
23787 {
23788 return;
23789 }
23790
23791 auto& shopGUI = players[player]->shopGUI;
23792
23793 if ( shopGUI.shopFrame || !players[player]->inventoryUI.frame )
23794 {
23795 return;
23796 }
23797
23798 SDL_Rect basePos{ 0, 0, 520, 334 };
23799
23800 Frame* frame = players[player]->inventoryUI.frame->addFrame("shop");
23801 shopGUI.shopFrame = frame;
23802 frame->setSize(SDL_Rect{ players[player]->camera_virtualx1(),
23803 players[player]->camera_virtualy1(),
23804 basePos.w,
23805 basePos.h });
23806 frame->setHollow(true);
23807 frame->setBorder(0);
23808 frame->setOwner(player);
23809 frame->setInheritParentFrameOpacity(false);
23810
23811 {
23812 auto bgFrame = frame->addFrame("shop base");
23813 bgFrame->setSize(basePos);
23814 bgFrame->setHollow(false);
23815 const auto bgSize = bgFrame->getSize();
23816 auto bg = bgFrame->addImage(SDL_Rect{ 0, 0, basePos.w, basePos.h },
23817 makeColor(255, 255, 255, 255),
23818 "*#images/ui/Shop/Shop_Window_03C.png", "shop base img");
23819 auto bgGrid = bgFrame->addImage(SDL_Rect{ 12, 18, 206, 214 },
23820 makeColor(255, 255, 255, 64),
23821 "*#images/ui/Shop/Shop_ItemSlots_Areas03.png", "shop grid img");
23822
23823 const char* font = "fonts/pixel_maz_multiline.ttf#16#2";
23824 Uint32 titleColor = makeColor(219, 157, 20, 255);
23825 Uint32 titleOutline = makeColor(29, 16, 11, 255);
23826 auto titleText = bgFrame->addField("shop name", 64);
23827 titleText->setFont(font);
23828 titleText->setText("");
23829 titleText->setHJustify(Field::justify_t::CENTER);
23830 titleText->setVJustify(Field::justify_t::TOP);
23831 titleText->setSize(SDL_Rect{ 228, 29, 184, 24 });
23832 titleText->setTextColor(titleColor);
23833 titleText->setOutlineColor(titleOutline);
23834
23835 const char* shoptypefont = "fonts/pixelmix.ttf#16#2";
23836 auto shopTypeText = bgFrame->addField("shop type", 64);
23837 shopTypeText->setFont(shoptypefont);
23838 shopTypeText->setText("");
23839 shopTypeText->setHJustify(Field::justify_t::CENTER);
23840 shopTypeText->setVJustify(Field::justify_t::TOP);
23841 shopTypeText->setSize(SDL_Rect{ 228, 48, 184, 82 });
23842 shopTypeText->setTextColor(titleColor);
23843 shopTypeText->setOutlineColor(titleOutline);
23844
23845 auto shopkeeperImg = bgFrame->addImage(SDL_Rect{ basePos.w - 14 - 80, 14, 80, 80 }, 0xFFFFFFFF,
23846 "*#images/ui/Shop/shopkeeper.png", "shopkeeper img");
23847
23848 auto closeBtn = bgFrame->addButton("close shop button");
23849 SDL_Rect closeBtnPos{ basePos.w - 34, 8, 26, 26 };
23850 closeBtn->setSize(closeBtnPos);
23851 closeBtn->setColor(makeColor(255, 255, 255, 255));
23852 closeBtn->setHighlightColor(makeColor(255, 255, 255, 255));
23853 closeBtn->setText("X");
23854 closeBtn->setFont(font);
23855 closeBtn->setHideGlyphs(true);
23856 closeBtn->setHideKeyboardGlyphs(true);
23857 closeBtn->setHideSelectors(true);
23858 closeBtn->setMenuConfirmControlType(0);
23859 closeBtn->setBackground("*#images/ui/Shop/Button_X_00.png");
23860 closeBtn->setBackgroundHighlighted("*#images/ui/Shop/Button_XHigh_00.png");
23861 closeBtn->setBackgroundActivated("*#images/ui/Shop/Button_XPress_00.png");
23862 closeBtn->setTextHighlightColor(makeColor(201, 162, 100, 255));
23863 closeBtn->setCallback([](Button& button) {
23864 closeShopGUIAction(button.getOwner());
23865 Player::soundCancel();
23866 });
23867
23868 auto discountFrame = bgFrame->addFrame("discount frame");
23869 discountFrame->setHollow(true);
23870 discountFrame->setBorder(0);
23871 discountFrame->setSize(SDL_Rect{ bgFrame->getSize().w - 112, bgFrame->getSize().h - 124, 98, 24 });
23872 discountFrame->setDisabled(false);
23873
23874 auto chatWindow = bgFrame->addFrame("chatter");
23875 auto tl = chatWindow->addImage(SDL_Rect{ 0, 0, 34, 34 }, 0xFFFFFFFF,
23876 "*#images/ui/Shop/Textbox_TLWings00.png", "top left img");
23877 auto tm = chatWindow->addImage(SDL_Rect{ 0, 0, 2, 6 }, 0xFFFFFFFF,
23878 "*#images/ui/Shop/Textbox_WingsT_00.png", "top img");
23879 auto tr = chatWindow->addImage(SDL_Rect{ 0, 0, 14, 28 }, 0xFFFFFFFF,
23880 "*#images/ui/Shop/Textbox_TR00.png", "top right img");
23881
23882 auto ml = chatWindow->addImage(SDL_Rect{ 0, 0, 6, 0 }, 0xFFFFFFFF,
23883 "*#images/ui/Shop/Textbox_WingsL_00.png", "middle left img");
23884 auto mm1 = chatWindow->addImage(SDL_Rect{ 0, 0, 2, 2 }, 0xFFFFFFFF,
23885 "*#images/ui/Shop/Textbox_CenterColor_00.png", "middle 1 img");
23886 auto mm2 = chatWindow->addImage(SDL_Rect{ 0, 0, 2, 2 }, 0xFFFFFFFF,
23887 "*#images/ui/Shop/Textbox_CenterColor_00.png", "middle 2 img");
23888 auto mr = chatWindow->addImage(SDL_Rect{ 0, 0, 6, 0 }, 0xFFFFFFFF,
23889 "*#images/ui/Shop/Textbox_WingsR_00.png", "middle right img");
23890
23891 auto bl = chatWindow->addImage(SDL_Rect{ 0, 0, 28, 14 }, 0xFFFFFFFF,
23892 "*#images/ui/Shop/Textbox_BL00.png", "bottom left img");
23893 auto bm = chatWindow->addImage(SDL_Rect{ 0, 0, 2, 6 }, 0xFFFFFFFF,
23894 "*#images/ui/Shop/Textbox_WingsB_00.png", "bottom img");
23895 auto br = chatWindow->addImage(SDL_Rect{ 0, 0, 14, 14 }, 0xFFFFFFFF,
23896 "*#images/ui/Shop/Textbox_BR00.png", "bottom right img");
23897
23898 auto pointer = chatWindow->addImage(SDL_Rect{ 0, 0, 20, 22 }, 0xFFFFFFFF,
23899 "*#images/ui/Shop/Textbox_SpeakerPointer_TR01.png", "pointer img");
23900
23901 auto bodyFont = "fonts/pixel_maz_multiline.ttf#16#2";
23902 auto chatText = chatWindow->addField("chat body", 1024);
23903 chatText->setFont(bodyFont);
23904 chatText->setText("");
23905 chatText->setHJustify(Field::justify_t::TOP);
23906 chatText->setVJustify(Field::justify_t::LEFT);
23907 chatText->setSize(SDL_Rect{ 0, 0, 0, 0 });
23908 //chatText->setColor(makeColor(29, 16, 11, 255));
23909 chatText->setTextColor(makeColor(29, 16, 11, 255));
23910 chatText->setOutlineColor(makeColor(0, 0, 0, 1));
23911
23912 {
23913 auto valueFont = "fonts/pixel_maz.ttf#32#2";
23914
23915 auto discountLabelText = bgFrame->addField("discount label", 32);
23916 discountLabelText->setFont(valueFont);
23917 discountLabelText->setText(Language::get(4122));
23918 discountLabelText->setHJustify(Field::justify_t::RIGHT);
23919 discountLabelText->setVJustify(Field::justify_t::TOP);
23920 discountLabelText->setSize(SDL_Rect{ bgFrame->getSize().w - 106 - 180 - 12, bgFrame->getSize().h - 92 - 30, 180, 24 });
23921 discountLabelText->setTextColor(makeColor(201, 162, 100, 255));
23922 discountLabelText->setOutlineColor(makeColor(29, 16, 11, 255));
23923
23924 auto discountImg = discountFrame->addImage(SDL_Rect{ 0, 0, 98, 24 }, 0xFFFFFFFF,
23925 "*#images/ui/Shop/Shop_DiscountLabel_00.png", "discount img");
23926
23927 auto discountValue = discountFrame->addField("discount", 32);
23928 discountValue->setFont(valueFont);
23929 discountValue->setText("");
23930 discountValue->setHJustify(Field::justify_t::RIGHT);
23931 discountValue->setVJustify(Field::justify_t::TOP);
23932 discountValue->setSize(SDL_Rect{ 14, 2, 80, 24 });
23933 discountValue->setColor(makeColor(201, 162, 100, 255));
23934
23935 auto currentGoldText = bgFrame->addField("current gold", 32);
23936 currentGoldText->setFont(valueFont);
23937 currentGoldText->setText("");
23938 currentGoldText->setHJustify(Field::justify_t::RIGHT);
23939 currentGoldText->setVJustify(Field::justify_t::TOP);
23940 currentGoldText->setSize(SDL_Rect{ bgFrame->getSize().w - 80 - 18, bgFrame->getSize().h - 92, 80, 24 });
23941 currentGoldText->setColor(makeColor(201, 162, 100, 255));
23942
23943 auto currentGoldLabelText = bgFrame->addField("current gold label", 32);
23944 currentGoldLabelText->setFont(valueFont);
23945 currentGoldLabelText->setText(Language::get(4119));
23946 currentGoldLabelText->setHJustify(Field::justify_t::RIGHT);
23947 currentGoldLabelText->setVJustify(Field::justify_t::TOP);
23948 currentGoldLabelText->setSize(SDL_Rect{ bgFrame->getSize().w - 106 - 100 - 12, bgFrame->getSize().h - 92, 100, 24 });
23949 currentGoldLabelText->setTextColor(makeColor(201, 162, 100, 255));
23950 currentGoldLabelText->setOutlineColor(makeColor(29, 16, 11, 255));
23951
23952 auto changeGoldText = bgFrame->addField("change gold", 32);
23953 changeGoldText->setFont(valueFont);
23954 changeGoldText->setText("");
23955 changeGoldText->setHJustify(Field::justify_t::RIGHT);
23956 changeGoldText->setVJustify(Field::justify_t::TOP);
23957 changeGoldText->setSize(SDL_Rect{ bgFrame->getSize().w - 80 - 18, bgFrame->getSize().h - 92, 80, 24 });
23958 changeGoldText->setColor(makeColor(233, 220, 70, 255));
23959 changeGoldText->setOntop(true);
23960 }
23961
23962 {
23963 auto promptFont = "fonts/pixel_maz.ttf#32#2";
23964 auto buybackText = bgFrame->addField("buyback txt", 32);
23965 buybackText->setFont(promptFont);
23966 buybackText->setText(Language::get(4120));
23967 buybackText->setHJustify(Field::justify_t::LEFT);
23968 buybackText->setVJustify(Field::justify_t::TOP);
23969 buybackText->setSize(SDL_Rect{ bgFrame->getSize().w - 178, bgFrame->getSize().h - 58, 178, 24 });
23970 buybackText->setTextColor(makeColor(201, 162, 100, 255));
23971 //buybackText->setOutlineColor(makeColor(29, 16, 11, 255));
23972 buybackText->setOutlineColor(makeColor(0, 0, 0, 255));
23973
23974 auto buybackGlyph = bgFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
23975 "", "buyback glyph");
23976 buybackGlyph->disabled = true;
23977
23978 auto buybackBtn = bgFrame->addButton("buyback button");
23979 buybackBtn->setSize(SDL_Rect{ bgFrame->getSize().w - 208, bgFrame->getSize().h - 50, 194, 26 });
23980 buybackBtn->setFont(promptFont);
23981 buybackBtn->setText(Language::get(4120));
23982 buybackBtn->setColor(makeColor(255, 255, 255, 255));
23983 buybackBtn->setHighlightColor(makeColor(255, 255, 255, 255));
23984 buybackBtn->setHideGlyphs(true);
23985 buybackBtn->setHideKeyboardGlyphs(true);
23986 buybackBtn->setHideSelectors(true);
23987 buybackBtn->setMenuConfirmControlType(0);
23988 buybackBtn->setBackground("*#images/ui/Shop/Shop_Buyback_Button_00.png");
23989 buybackBtn->setBackgroundHighlighted("*#images/ui/Shop/Shop_Buyback_ButtonHigh_00.png");
23990 buybackBtn->setBackgroundActivated("*#images/ui/Shop/Shop_Buyback_ButtonPress_00.png");
23991 buybackBtn->setTextHighlightColor(makeColor(201, 162, 100, 255));
23992 buybackBtn->setCallback([](Button& button) {
23993 toggleShopBuybackView(button.getOwner());
23994 Player::soundActivate();
23995 });
23996
23997 auto closeText = bgFrame->addField("close shop prompt", 32);
23998 closeText->setFont(promptFont);
23999 closeText->setText(Language::get(4121));
24000 closeText->setHJustify(Field::justify_t::LEFT);
24001 closeText->setVJustify(Field::justify_t::TOP);
24002 closeText->setSize(SDL_Rect{ bgFrame->getSize().w - 178, bgFrame->getSize().h - 36, 178, 24 });
24003 closeText->setTextColor(makeColor(201, 162, 100, 255));
24004 //closeText->setOutlineColor(makeColor(29, 16, 11, 255));
24005 closeText->setOutlineColor(makeColor(0, 0, 0, 255));
24006
24007 auto closeGlyph = bgFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
24008 "", "close shop glyph");
24009 closeGlyph->disabled = true;
24010 //closeText->setColor(makeColor(233, 220, 70, 255));
24011 }
24012 }
24013
24014 const int inventorySlotSize = players[player]->inventoryUI.getSlotSize();
24015
24016 shopGUI.shopSlotFrames.clear();
24017
24018 const int baseSlotOffsetX = 0;
24019 const int baseSlotOffsetY = 0;
24020
24021 SDL_Rect invSlotsPos{ 12, 16, 208, 216 };
24022 {
24023 const auto shopSlotsFrame = frame->addFrame("shop slots");
24024 shopSlotsFrame->setSize(invSlotsPos);
24025 shopSlotsFrame->setHollow(true);
24026
24027 /*auto gridImg = chestSlotsFrame->addImage(SDL_Rect{ baseSlotOffsetX, baseSlotOffsetY, 162, gridHeight * numGrids },
24028 makeColor(255, 255, 255, 32), "*#images/ui/Inventory/HUD_Chest4x3_ScrollGrid.png", "grid img");
24029 gridImg->tiled = true;*/
24030
24031 SDL_Rect currentSlotPos{ baseSlotOffsetX, baseSlotOffsetY, inventorySlotSize, inventorySlotSize };
24032 const int maxShopX = Player::ShopGUI_t::MAX_SHOP_X;
24033 const int maxShopY = Player::ShopGUI_t::MAX_SHOP_Y;
24034
24035 int accumulateSlotOffsetX = 0;
24036 for ( int x = 0; x < maxShopX; ++x )
24037 {
24038 currentSlotPos.x = baseSlotOffsetX + (x * inventorySlotSize) + accumulateSlotOffsetX;
24039 for ( int y = 0; y < maxShopY; ++y )
24040 {
24041 currentSlotPos.y = baseSlotOffsetY + (y * (inventorySlotSize + 4));
24042
24043 char slotname[32] = "";
24044 snprintf(slotname, sizeof(slotname), "shop %d %d", x, y);
24045
24046 auto slotFrame = shopSlotsFrame->addFrame(slotname);
24047 shopGUI.shopSlotFrames[x + y * 100] = slotFrame;
24048 SDL_Rect slotPos{ currentSlotPos.x, currentSlotPos.y, inventorySlotSize, inventorySlotSize };
24049 slotFrame->setSize(slotPos);
24050
24051 createPlayerInventorySlotFrameElements(slotFrame);
24052 }
24053
24054 if ( x == 0 || x == 3 )
24055 {
24056 accumulateSlotOffsetX += 4;
24057 }
24058 }
24059 }
24060
24061 {
24062 auto buyTooltipFrame = frame->addFrame("buy tooltip frame");
24063 buyTooltipFrame->setHollow(true);
24064 buyTooltipFrame->setBorder(0);
24065 buyTooltipFrame->setSize(SDL_Rect{ 4, basePos.h - 66, 310, 66 });
24066 buyTooltipFrame->setDisabled(true);
24067
24068 auto itemTooltipImg = buyTooltipFrame->addImage(SDL_Rect{ 0, 0, 310, 66 }, 0xFFFFFFFF,
24069 "*#images/ui/Shop/Shop_Tooltip_2Row_00.png", "tooltip img");
24070
24071 auto itemGoldImg = buyTooltipFrame->addImage(SDL_Rect{ 0, 0, 20, 28 }, 0xFFFFFFFF,
24072 "*#images/ui/Inventory/tooltips/HUD_Tooltip_Icon_Money_00.png", "gold img");
24073
24074 auto itemBgImg = buyTooltipFrame->addImage(SDL_Rect{ 0, 0, 54, 54 }, 0xFFFFFFFF,
24075 "*images/ui/Shop/Shop_Buy_BGSurround03.png", "item bg img");
24076
24077 auto orbImg = buyTooltipFrame->addImage(SDL_Rect{ 210 - 8, 38, 16, 16 }, 0xFFFFFFFF,
24078 "", "orb img");
24079 orbImg->disabled = true;
24080
24081 auto slotFrame = buyTooltipFrame->addFrame("item slot frame");
24082 SDL_Rect slotPos{ 0, 0, players[player]->inventoryUI.getSlotSize(), players[player]->inventoryUI.getSlotSize() };
24083 slotFrame->setSize(slotPos);
24084 slotFrame->setDisabled(true);
24085 createPlayerInventorySlotFrameElements(slotFrame);
24086
24087 auto itemFont = "fonts/pixel_maz_multiline.ttf#16#2";
24088 auto itemNameText = buyTooltipFrame->addField("item display name", 1024);
24089 itemNameText->setFont(itemFont);
24090 itemNameText->setText("");
24091 itemNameText->setHJustify(Field::justify_t::LEFT);
24092 itemNameText->setVJustify(Field::justify_t::TOP);
24093 itemNameText->setSize(SDL_Rect{ 0, 0, 0, 0 });
24094 itemNameText->setColor(hudColors.characterSheetLightNeutral);
24095 auto itemValueText = buyTooltipFrame->addField("item display value", 1024);
24096 itemValueText->setFont(itemFont);
24097 itemValueText->setText("");
24098 itemValueText->setHJustify(Field::justify_t::LEFT);
24099 itemValueText->setVJustify(Field::justify_t::TOP);
24100 itemValueText->setSize(SDL_Rect{ 0, 0, 0, 0 });
24101 itemValueText->setColor(makeColor(201, 162, 100, 255));
24102
24103 auto buyPromptText = buyTooltipFrame->addField("buy prompt txt", 128);
24104 buyPromptText->setFont(itemFont);
24105 buyPromptText->setText("");
24106 buyPromptText->setHJustify(Field::justify_t::LEFT);
24107 buyPromptText->setVJustify(Field::justify_t::TOP);
24108 buyPromptText->setSize(SDL_Rect{ 0, 0, 0, 0 });
24109 buyPromptText->setColor(makeColor(255, 255, 255, 255));
24110 auto buyPromptFrame = buyTooltipFrame->addFrame("buy prompt frame");
24111 buyPromptFrame->setHollow(true);
24112 buyPromptFrame->setBorder(0);
24113 buyPromptFrame->setSize(SDL_Rect{ 0, 0, 0, 0 });
24114 buyPromptFrame->setDisabled(true);
24115 auto buyPromptGlyph = buyPromptFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
24116 "", "buy prompt glyph");
24117 }
24118
24119 if ( auto buttomEdgeCoverFrame = frame->addFrame("shop bottom edge frame") )
24120 {
24121 buttomEdgeCoverFrame->setSize(SDL_Rect{ 0, basePos.h - 10, basePos.w, 10 });
24122 buttomEdgeCoverFrame->setHollow(true);
24123 buttomEdgeCoverFrame->setBorder(0);
24124 auto bottomEdgeCover = buttomEdgeCoverFrame->addImage(SDL_Rect{ 0, 0, basePos.w, 10 },
24125 makeColor(255, 255, 255, 255),
24126 "*#images/ui/Shop/Shop_BottomEdgeCover_00.png", "shop bottom edge img");
24127 }
24128}
24129
24130void createPlayerInventory(const int player)
24131{
24132 char name[32];
24133 snprintf(name, sizeof(name), "player inventory %d", player);
24134 Frame* frame = gameUIFrame[player]->addFrame(name);
24135 players[player]->inventoryUI.frame = frame;
24136 frame->setSize(SDL_Rect{ players[player]->camera_virtualx1(),
24137 players[player]->camera_virtualy1(),
24138 players[player]->camera_virtualWidth(),
24139 players[player]->camera_virtualHeight() });
24140 frame->setHollow(true);
24141 frame->setBorder(0);
24142 frame->setOwner(player);
24143 frame->setInheritParentFrameOpacity(false);
24144
24145 createChestGUI(player);
24146 createShopGUI(player);
24147
24148 SDL_Rect basePos{ 0, 0, 210, 448 };
24149 {
24150 auto bgFrame = frame->addFrame("inventory base");
24151 playerInventoryFrames[player].inventoryBaseImagesFrame = bgFrame;
24152 bgFrame->setSize(basePos);
24153 bgFrame->setHollow(true);
24154 const auto bgSize = bgFrame->getSize();
24155 auto defaultInvImg = bgFrame->addImage(SDL_Rect{ 0, 0, bgSize.w, 448 },
24156 makeColor( 255, 255, 255, 255),
24157 "*#images/ui/Inventory/HUD_Inventory_Base_02.png", "inventory base img");
24158 defaultInvImg->disabled = true;
24159 playerInventoryFrames[player].defaultInvImg = defaultInvImg;
24160
24161 auto compactBase = bgFrame->addImage(SDL_Rect{ 0, 0, 210, 250 },
24162 makeColor( 255, 255, 255, 255),
24163 "*#images/ui/Inventory/HUD_Inventory_BaseCompact_02.png", "inventory base compact img");
24164 compactBase->disabled = true;
24165 playerInventoryFrames[player].compactInvImg = compactBase;
24166
24167 auto compactCharacterView = bgFrame->addImage(SDL_Rect{ 0, 0, 210, 214 },
24168 makeColor( 255, 255, 255, 255),
24169 "*#images/ui/Inventory/HUD_Inventory_CharacterCompact_02.png", "inventory character compact img");
24170 compactCharacterView->disabled = true;
24171 playerInventoryFrames[player].compactCharImg = compactCharacterView;
24172 }
24173
24174 {
24175 auto backpackFrame = frame->addFrame("inventory backpack");
24176 playerInventoryFrames[player].backpackFrame = backpackFrame;
24177 backpackFrame->setSize(SDL_Rect{ 0, 202 + 242 - 2, 226, 102 });
24178 auto backpackImg = backpackFrame->addImage(SDL_Rect{ 0, 0, 226, 102 },
24179 makeColor( 255, 255, 255, 255),
24180 "*#images/ui/Inventory/HUD_Inventory_Base_Bag_00a.png", "inventory backpack img");
24181 }
24182
24183 const int inventorySlotSize = players[player]->inventoryUI.getSlotSize();
24184
24185 players[player]->inventoryUI.slotFrames.clear();
24186
24187 const int baseSlotOffsetX = 4;
24188 const int baseSlotOffsetY = 0;
24189 SDL_Rect invSlotsPos{ 0, 202, basePos.w, 242 };
24190 {
24191 const auto invSlotsFrame = frame->addFrame("inventory slots");
24192 playerInventoryFrames[player].invSlotsFrame = invSlotsFrame;
24193 invSlotsFrame->setSize(invSlotsPos);
24194
24195 SDL_Rect currentSlotPos{ baseSlotOffsetX, baseSlotOffsetY, inventorySlotSize, inventorySlotSize };
24196
24197 for ( int x = 0; x < players[player]->inventoryUI.getSizeX(); ++x )
24198 {
24199 currentSlotPos.x = baseSlotOffsetX + (x * inventorySlotSize);
24200 for ( int y = 0; y < players[player]->inventoryUI.DEFAULT_INVENTORY_SIZEY; ++y )
24201 {
24202 currentSlotPos.y = baseSlotOffsetY + (y * inventorySlotSize);
24203
24204 char slotname[32] = "";
24205 snprintf(slotname, sizeof(slotname), "slot %d %d", x, y);
24206
24207 auto slotFrame = invSlotsFrame->addFrame(slotname);
24208 players[player]->inventoryUI.slotFrames[x + y * 100] = slotFrame;
24209 SDL_Rect slotPos{ currentSlotPos.x, currentSlotPos.y, inventorySlotSize, inventorySlotSize };
24210 slotFrame->setSize(slotPos);
24211 //slotFrame->setDisabled(true);
24212
24213 createPlayerInventorySlotFrameElements(slotFrame);
24214 }
24215 }
24216 //invSlotsFrame->setBlitChildren(true);
24217 }
24218
24219 SDL_Rect backpackSlotsPos{ 0, 202 + 242, basePos.w, 242 };
24220 {
24221 const auto backPackSlotsFrame = frame->addFrame("backpack slots");
24222 playerInventoryFrames[player].backpackSlotsFrame = backPackSlotsFrame;
24223 backPackSlotsFrame->setSize(backpackSlotsPos);
24224 const int backpackBaseSlotOffsetY = 8;
24225 int lowestY = 0;
24226
24227 SDL_Rect currentSlotPos{ baseSlotOffsetX, backpackBaseSlotOffsetY, inventorySlotSize, inventorySlotSize };
24228
24229 for ( int x = 0; x < players[player]->inventoryUI.getSizeX(); ++x )
24230 {
24231 currentSlotPos.x = baseSlotOffsetX + (x * inventorySlotSize);
24232 if ( x == 0 ) { currentSlotPos.x -= 4; } // backpack has unique first/last column entries
24233 if ( x == players[player]->inventoryUI.getSizeX() - 1 ) { currentSlotPos.x += 4; }
24234
24235 for ( int y = players[player]->inventoryUI.DEFAULT_INVENTORY_SIZEY;
24236 y < players[player]->inventoryUI.DEFAULT_INVENTORY_SIZEY + players[player]->inventoryUI.getPlayerBackpackBonusSizeY(); ++y )
24237 {
24238 currentSlotPos.y = backpackBaseSlotOffsetY + ((y - players[player]->inventoryUI.DEFAULT_INVENTORY_SIZEY) * inventorySlotSize);
24239
24240 char slotname[32] = "";
24241 snprintf(slotname, sizeof(slotname), "slot %d %d", x, y);
24242
24243 auto slotFrame = backPackSlotsFrame->addFrame(slotname);
24244 players[player]->inventoryUI.slotFrames[x + y * 100] = slotFrame;
24245 SDL_Rect slotPos{ currentSlotPos.x, currentSlotPos.y, inventorySlotSize, inventorySlotSize };
24246 slotFrame->setSize(slotPos);
24247 lowestY = std::max(slotPos.y + slotPos.h, lowestY);
24248 //slotFrame->setDisabled(true);
24249
24250 createPlayerInventorySlotFrameElements(slotFrame);
24251 }
24252 }
24253 backpackSlotsPos.h = lowestY;
24254 backPackSlotsFrame->setSize(backpackSlotsPos);
24255 }
24256
24257 {
24258 SDL_Rect dollSlotsPos{ 0, 0, basePos.w, invSlotsPos.y };
24259 const auto dollSlotsFrame = frame->addFrame("paperdoll slots");
24260 playerInventoryFrames[player].dollSlotsFrame = dollSlotsFrame;
24261 dollSlotsFrame->setSize(dollSlotsPos);
24262
24263 SDL_Rect currentSlotPos{ baseSlotOffsetX, baseSlotOffsetY, inventorySlotSize, inventorySlotSize };
24264
24265 for ( int x = Player::Inventory_t::PaperDollColumns::DOLL_COLUMN_LEFT; x <= Player::Inventory_t::PaperDollColumns::DOLL_COLUMN_RIGHT; ++x )
24266 {
24267 currentSlotPos.x = baseSlotOffsetX;
24268 if ( x == Player::Inventory_t::PaperDollColumns::DOLL_COLUMN_RIGHT )
24269 {
24270 currentSlotPos.x = baseSlotOffsetX + (4 * inventorySlotSize); // 4 slots over
24271 }
24272
24273 for ( int y = Player::Inventory_t::PaperDollRows::DOLL_ROW_1; y <= Player::Inventory_t::PaperDollRows::DOLL_ROW_5; ++y )
24274 {
24275 currentSlotPos.y = baseSlotOffsetY + ((y - Player::Inventory_t::PaperDollRows::DOLL_ROW_1) * inventorySlotSize);
24276
24277 char slotname[32] = "";
24278 snprintf(slotname, sizeof(slotname), "slot %d %d", x, y);
24279
24280 auto slotFrame = dollSlotsFrame->addFrame(slotname);
24281 players[player]->inventoryUI.slotFrames[x + y * 100] = slotFrame;
24282 SDL_Rect slotPos{ currentSlotPos.x, currentSlotPos.y, inventorySlotSize, inventorySlotSize };
24283 slotFrame->setSize(slotPos);
24284 //slotFrame->setDisabled(true);
24285
24286 createPlayerInventorySlotFrameElements(slotFrame);
24287 }
24288 }
24289
24290 {
24291 auto charFrame = frame->addFrame("inventory character preview");
24292 playerInventoryFrames[player].characterPreview = charFrame;
24293 auto charSize = dollSlotsPos;
24294 charSize.x += inventorySlotSize + baseSlotOffsetX + 4;
24295 charSize.w -= 2 * (inventorySlotSize + baseSlotOffsetX + 4);
24296 charFrame->setSize(charSize);
24297 charFrame->setTickCallback([](Widget& widget) {
24298 Frame* frame = static_cast<Frame*>(&widget);
24299 int player = widget.getOwner();
24300 auto& scrollInertia = players[player]->paperDoll.portraitRotationInertia;
24301 auto& scrollPercent = players[player]->paperDoll.portraitRotationPercent;
24302 auto& portraitYaw = players[player]->paperDoll.portraitYaw;
24303 static ConsoleVariable<float> cvar_char_portrait_spd("/char_portrait_spd", 15.0);
24304 static ConsoleVariable<float> cvar_char_portrait_decel("/char_portrait_decel", 25.0);
24305
24306 bool close = false;
24307 if ( players[player]->GUI.activeModule == Player::GUI_t::MODULE_PORTRAIT )
24308 {
24309 SDL_Rect size = frame->getAbsoluteSize();
24310
24311 // make sure to adjust absolute size to camera viewport
24312 const int offsetX = 4;
24313 const int offsetY = 6;
24314 size.x += offsetX;
24315 size.y += offsetY;
24316 size.w -= offsetX * 2;
24317 size.h -= offsetY * 2;
24318 size.x -= players[player]->camera_virtualx1();
24319 size.y -= players[player]->camera_virtualy1();
24320
24321 players[player]->hud.updateCursorAnimation(size.x - 1, size.y - 1,
24322 size.w, size.h, inputs.getVirtualMouse(player)->draw_cursor);
24323 players[player]->paperDoll.portraitActiveToEdit = true;
24324
24325 if ( inputs.bPlayerUsingKeyboardControl(player) )
24326 {
24327 if ( Input::inputs[player].binaryToggle("InventoryCharacterRotateLeftMouse") )
24328 {
24329 scrollInertia = std::min(scrollInertia + .05 / *cvar_char_portrait_spd, .05);
24330 }
24331 if ( Input::inputs[player].binaryToggle("InventoryCharacterRotateRightMouse") )
24332 {
24333 scrollInertia = std::max(scrollInertia - .05 / *cvar_char_portrait_spd, -.05);
24334 }
24335 close |= Input::inputs[player].consumeBinaryToggle("MenuLeftClick");
24336 close |= Input::inputs[player].consumeBinaryToggle("MenuRightClick");
24337 }
24338 if ( inputs.hasController(player) )
24339 {
24340 close |= Input::inputs[player].consumeBinaryToggle("MenuConfirm");
24341 close |= Input::inputs[player].consumeBinaryToggle("MenuCancel");
24342 }
24343 if ( close )
24344 {
24345 Player::soundCancel();
24346 }
24347
24348 if ( players[player]->shootmode )
24349 {
24350 close = true;
24351 }
24352
24353 if ( Input::inputs[player].analog("InventoryCharacterRotateRight") )
24354 {
24355 scrollInertia = 0.0;
24356 real_t delta = Input::inputs[player].analog("InventoryCharacterRotateRight");
24357 scrollPercent = (scrollPercent + .05 * (getFPSScale(60.0)) * delta);
24358 while ( scrollPercent >= 1.0 )
24359 {
24360 scrollPercent -= 1.0;
24361 }
24362 }
24363 else if ( Input::inputs[player].analog("InventoryCharacterRotateLeft") )
24364 {
24365 scrollInertia = 0.0;
24366 real_t delta = Input::inputs[player].analog("InventoryCharacterRotateLeft");
24367 scrollPercent = scrollPercent - .05 * (getFPSScale(60.0)) * delta;
24368 while ( scrollPercent < 0.0 )
24369 {
24370 scrollPercent += 1.0;
24371 }
24372 }
24373
24374 if ( abs(scrollInertia) > 0.0 )
24375 {
24376 scrollInertia *= .9;
24377 if ( abs(scrollInertia) < (.01 / *cvar_char_portrait_decel) )
24378 {
24379 scrollInertia = 0.0;
24380 }
24381 scrollPercent += scrollInertia;
24382 }
24383
24384 if ( Input::inputs[player].consumeBinaryToggle("ResetPortraitRotation") )
24385 {
24386 Input::inputs[player].consumeBindingsSharedWithBinding("ResetPortraitRotation");
24387 scrollPercent = 0.0;
24388 scrollInertia = 0.0;
24389 portraitYaw = (330) * PI3.14159265358979323846 / 180;
24390 close = true;
24391 Player::soundCancel();
24392 }
24393 }
24394
24395 if ( close )
24396 {
24397 if ( !inputs.getVirtualMouse(player)->draw_cursor )
24398 {
24399 players[player]->GUI.returnToPreviousActiveModule();
24400 }
24401 else
24402 {
24403 players[player]->GUI.activateModule(Player::GUI_t::MODULE_NONE);
24404 }
24405 }
24406 if ( players[player]->GUI.activeModule != Player::GUI_t::MODULE_PORTRAIT )
24407 {
24408 players[player]->paperDoll.portraitActiveToEdit = false;
24409 }
24410 portraitYaw = (330) * PI3.14159265358979323846 / 180 + (scrollPercent * 2 * PI3.14159265358979323846);
24411 if ( portraitYaw > 2 * PI3.14159265358979323846 )
24412 {
24413 portraitYaw -= 2 * PI3.14159265358979323846;
24414 }
24415 if ( portraitYaw < 0.0 )
24416 {
24417 portraitYaw += 2 * PI3.14159265358979323846;
24418 }
24419 });
24420 charFrame->setDrawCallback([](const Widget& widget, SDL_Rect pos) {
24421 drawCharacterPreview(widget.getOwner(), pos, 50, players[widget.getOwner()]->paperDoll.portraitYaw);
24422 });
24423
24424 /*charFrame->addImage(SDL_Rect{ 0, 0, charSize.w, charSize.h },
24425 makeColor( 255, 255, 255, 255),
24426 "images/system/white.png", "inventory character preview bg");*/
24427 }
24428 }
24429
24430 createPlayerSpellList(player);
24431
24432 {
24433 auto selectedFrame = frame->addFrame("inventory selected item");
24434 playerInventoryFrames[player].selectedSlotFrame = selectedFrame;
24435 selectedFrame->setSize(SDL_Rect{ 0, 0, inventorySlotSize, inventorySlotSize });
24436 selectedFrame->setDisabled(true);
24437
24438 Uint32 color = makeColor( 255, 255, 0, 255);
24439 selectedFrame->addImage(SDL_Rect{ 0, 0, selectedFrame->getSize().w, selectedFrame->getSize().h },
24440 color, "*images/system/hotbar_slot.png", "inventory selected highlight");
24441
24442 auto oldSelectedFrame = frame->addFrame("inventory old selected item");
24443 oldSelectedFrame->setSize(SDL_Rect{ 0, 0, inventorySlotSize, inventorySlotSize });
24444 oldSelectedFrame->setDisabled(true);
24445 playerInventoryFrames[player].oldSelectedSlotFrame = oldSelectedFrame;
24446
24447 const int itemSpriteSize = players[player]->inventoryUI.getItemSpriteSize();
24448 SDL_Rect itemSpriteBorder{ 2, 2, itemSpriteSize, itemSpriteSize };
24449
24450 color = makeColor( 0, 255, 255, 255);
24451 auto oldImg = oldSelectedFrame->addImage(itemSpriteBorder,
24452 makeColor( 255, 255, 255, 128), "", "inventory old selected item");
24453 playerInventoryFrames[player].oldSelectedSlotItemImg = oldImg;
24454 oldImg->disabled = true;
24455 oldSelectedFrame->addImage(SDL_Rect{ 0, 0, oldSelectedFrame->getSize().w, oldSelectedFrame->getSize().h },
24456 color, "*images/system/hotbar_slot.png", "inventory old selected highlight");
24457
24458 auto bgFrame = frame->findFrame("inventory base");
24459 playerInventoryFrames[player].inventoryBgFrame = bgFrame;
24460 auto flourishFrame = frame->addFrame("inventory base flourish");
24461 playerInventoryFrames[player].flourishFrame = flourishFrame;
24462 const int flourishW = 126;
24463 const int flourishH = 26;
24464 flourishFrame->setHollow(true);
24465 flourishFrame->setSize(SDL_Rect{ (bgFrame->getSize().w / 2) - (flourishW / 2), 202 - flourishH + 6, flourishW, flourishH });
24466 auto flourishImg = flourishFrame->addImage(SDL_Rect{ 0, 0, flourishFrame->getSize().w, flourishFrame->getSize().h },
24467 makeColor( 255, 255, 255, 255),
24468 "*#images/ui/Inventory/HUD_Inventory_Flourish_00.png", "inventory flourish img");
24469 //flourishImg->disabled = true;
24470
24471 {
24472 SDL_Rect charSize = playerInventoryFrames[player].characterPreview->getSize();
24473 auto autosortFrame = frame->addFrame("autosort frame");
24474 playerInventoryFrames[player].autosortFrame = autosortFrame;
24475 autosortFrame->setHollow(true);
24476 autosortFrame->setSize(charSize);
24477 auto autosortButton = autosortFrame->addButton("autosort button");
24478 autosortButton->setSize(SDL_Rect{ charSize.w - 26,
24479 charSize.h - 26 - 16, 26, 26 });
24480 autosortButton->setBackground("*#images/ui/Inventory/HUD_Button_AutosortUnselect.png");
24481 autosortButton->setBackgroundActivated("*#images/ui/Inventory/HUD_Button_AutosortPress.png");
24482 autosortButton->setBackgroundHighlighted("*#images/ui/Inventory/HUD_Button_AutosortSelect.png");
24483 autosortButton->setHideGlyphs(true);
24484 autosortButton->setHideKeyboardGlyphs(true);
24485 autosortButton->setHideSelectors(true);
24486 autosortButton->setMenuConfirmControlType(0);
24487 //autosortButton->setOntop(true);
24488 autosortButton->setColor(makeColor(255, 255, 255, 255));
24489 autosortButton->setHighlightColor(makeColor(255, 255, 255, 255));
24490 autosortButton->setCallback([](Button& button) {
24491 autosortInventory(button.getOwner());
24492 //playSound(139, 64);
24493 Player::soundActivate();
24494 });
24495 autosortButton->setTickCallback([](Widget& widget) {
24496 if ( widget.isSelected()
24497 && (players[widget.getOwner()]->GUI.activeModule != Player::GUI_t::MODULE_INVENTORY
24498 || players[widget.getOwner()]->shootmode
24499 || !inputs.getVirtualMouse(widget.getOwner())->draw_cursor) )
24500 {
24501 widget.deselect();
24502 }
24503 });
24504 auto autosortImg = autosortFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "autosort glyph");
24505 autosortImg->disabled = true;
24506 autosortImg->ontop = true;
24507 }
24508
24509 GenericGUI[player].tinkerGUI.tinkerFrame = frame->addFrame("tinker");
24510 GenericGUI[player].tinkerGUI.tinkerFrame->setHollow(true);
24511 GenericGUI[player].tinkerGUI.tinkerFrame->setBorder(0);
24512 GenericGUI[player].tinkerGUI.tinkerFrame->setOwner(player);
24513 GenericGUI[player].tinkerGUI.tinkerFrame->setInheritParentFrameOpacity(false);
24514 GenericGUI[player].tinkerGUI.tinkerFrame->setDisabled(true);
24515
24516 GenericGUI[player].alchemyGUI.alchFrame = frame->addFrame("alchemy");
24517 GenericGUI[player].alchemyGUI.alchFrame->setHollow(true);
24518 GenericGUI[player].alchemyGUI.alchFrame->setBorder(0);
24519 GenericGUI[player].alchemyGUI.alchFrame->setOwner(player);
24520 GenericGUI[player].alchemyGUI.alchFrame->setInheritParentFrameOpacity(false);
24521 GenericGUI[player].alchemyGUI.alchFrame->setDisabled(true);
24522
24523 GenericGUI[player].featherGUI.featherFrame = frame->addFrame("feather");
24524 GenericGUI[player].featherGUI.featherFrame->setHollow(true);
24525 GenericGUI[player].featherGUI.featherFrame->setBorder(0);
24526 GenericGUI[player].featherGUI.featherFrame->setOwner(player);
24527 GenericGUI[player].featherGUI.featherFrame->setInheritParentFrameOpacity(false);
24528 GenericGUI[player].featherGUI.featherFrame->setDisabled(true);
24529
24530 GenericGUI[player].itemfxGUI.itemEffectFrame = frame->addFrame("itemfx");
24531 GenericGUI[player].itemfxGUI.itemEffectFrame->setHollow(true);
24532 GenericGUI[player].itemfxGUI.itemEffectFrame->setBorder(0);
24533 GenericGUI[player].itemfxGUI.itemEffectFrame->setOwner(player);
24534 GenericGUI[player].itemfxGUI.itemEffectFrame->setInheritParentFrameOpacity(false);
24535 GenericGUI[player].itemfxGUI.itemEffectFrame->setDisabled(true);
24536
24537 auto oldCursorFrame = frame->addFrame("inventory old item cursor");
24538 oldCursorFrame->setSize(SDL_Rect{ 0, 0, inventorySlotSize + 16, inventorySlotSize + 16 });
24539 oldCursorFrame->setDisabled(true);
24540 oldCursorFrame->setHollow(true);
24541 color = makeColor( 255, 255, 255, oldSelectedCursorOpacity);
24542 oldCursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
24543 color, "*#images/ui/Inventory/SelectorGrey_TL.png", "inventory old cursor topleft");
24544 oldCursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
24545 color, "*#images/ui/Inventory/SelectorGrey_TR.png", "inventory old cursor topright");
24546 oldCursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
24547 color, "*#images/ui/Inventory/SelectorGrey_BL.png", "inventory old cursor bottomleft");
24548 oldCursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
24549 color, "*#images/ui/Inventory/SelectorGrey_BR.png", "inventory old cursor bottomright");
24550
24551 auto cursorFrame = frame->addFrame("inventory selected item cursor");
24552 players[player]->inventoryUI.selectedItemCursorFrame = cursorFrame;
24553 cursorFrame->setSize(SDL_Rect{ 0, 0, inventorySlotSize + 16, inventorySlotSize + 16 });
24554 cursorFrame->setDisabled(true);
24555 cursorFrame->setHollow(true);
24556 color = makeColor( 255, 255, 255, selectedCursorOpacity);
24557 cursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
24558 color, "*#images/ui/Inventory/Selector_TL.png", "inventory selected cursor topleft");
24559 cursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
24560 color, "*#images/ui/Inventory/Selector_TR.png", "inventory selected cursor topright");
24561 cursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
24562 color, "*#images/ui/Inventory/Selector_BL.png", "inventory selected cursor bottomleft");
24563 cursorFrame->addImage(SDL_Rect{ 0, 0, 14, 14 },
24564 color, "*#images/ui/Inventory/Selector_BR.png", "inventory selected cursor bottomright");
24565 }
24566
24567 {
24568 auto draggingInventoryItem = frame->addFrame("dragging inventory item");
24569 draggingInventoryItem->setSize(SDL_Rect{ 0, 0, inventorySlotSize, inventorySlotSize });
24570 draggingInventoryItem->setDisabled(true);
24571 createPlayerInventorySlotFrameElements(draggingInventoryItem);
24572 }
24573
24574 {
24575 // unused for now, only animating cycling items
24576 auto draggingInventoryItemOld = frame->addFrame("dragging inventory item old");
24577 const int itemSpriteSize = players[player]->inventoryUI.getItemSpriteSize();
24578 SDL_Rect draggingInventoryItemPos{ 2, 2, inventorySlotSize - 2, inventorySlotSize - 2 };
24579 const int alignOffset = (draggingInventoryItemPos.w - itemSpriteSize) / 2; // align the item sprite within the box by this offset to center
24580 draggingInventoryItemPos.x += alignOffset;
24581 draggingInventoryItemPos.y += alignOffset;
24582 draggingInventoryItemPos.w = itemSpriteSize;
24583 draggingInventoryItemPos.h = itemSpriteSize;
24584 draggingInventoryItemOld->setSize(draggingInventoryItemPos);
24585 draggingInventoryItemOld->setDisabled(true);
24586
24587 SDL_Rect imgPos{ 0, 0, draggingInventoryItemOld->getSize().w, draggingInventoryItemOld->getSize().h };
24588 auto itemSprite = draggingInventoryItemOld->addImage(imgPos, 0xFFFFFFFF, "", "item sprite img");
24589 }
24590}
24591
24592void Player::Inventory_t::updateSelectedSlotAnimation(int destx, int desty, int width, int height, bool usingMouse)
24593{
24594 if ( frame )
24595 {
24596 if ( selectedItemCursorFrame )
24597 {
24598 if ( usingMouse )
24599 {
24600 selectedItemCursorFrame->setSize(
24601 SDL_Rect{
24602 destx - cursor.cursorToSlotOffset,
24603 desty - cursor.cursorToSlotOffset,
24604 width + 2 * (cursor.cursorToSlotOffset + 1),
24605 height + 2 * (cursor.cursorToSlotOffset + 1)
24606 }
24607 );
24608 cursor.animateSetpointX = destx;
24609 cursor.animateSetpointY = desty;
24610 cursor.animateStartX = destx;
24611 cursor.animateStartY = desty;
24612 }
24613 else if ( cursor.animateSetpointX != destx || cursor.animateSetpointY != desty )
24614 {
24615 SDL_Rect size = selectedItemCursorFrame->getSize();
24616 cursor.animateStartX = size.x;
24617 cursor.animateStartY = size.y;
24618 size.w = width + 2 * (cursor.cursorToSlotOffset + 1);
24619 size.h = height + 2 * (cursor.cursorToSlotOffset + 1);
24620 selectedItemCursorFrame->setSize(size);
24621 cursor.animateSetpointX = destx;
24622 cursor.animateSetpointY = desty;
24623 cursor.animateX = 0.0;
24624 cursor.animateY = 0.0;
24625 cursor.lastUpdateTick = ticks;
24626 }
24627 }
24628 }
24629 //messagePlayer(player.playernum, "%d %d", destx, desty);
24630}
24631
24632void Player::Inventory_t::updateItemContextMenu()
24633{
24634 Uint32& itemMenuItem = inputs.getUIInteraction(player.playernum)->itemMenuItem;
24635 bool& itemMenuOpen = inputs.getUIInteraction(player.playernum)->itemMenuOpen;
24636 int& itemMenuSelected = inputs.getUIInteraction(player.playernum)->itemMenuSelected;
24637 bool& itemMenuFromHotbar = inputs.getUIInteraction(player.playernum)->itemMenuFromHotbar;
24638
24639 Item* item = uidToItem(itemMenuItem);
24640 if ( itemMenuItem != 0 && !item )
24641 {
24642 if ( chestGUI.bOpen && openedChest[player.playernum] )
24643 {
24644 list_t* chest_inventory = nullptr;
24645 if ( multiplayer == CLIENT2 )
24646 {
24647 chest_inventory = &chestInv[player.playernum];
24648 }
24649 else if ( openedChest[player.playernum]->children.first && openedChest[player.playernum]->children.first->element )
24650 {
24651 chest_inventory = (list_t*)openedChest[player.playernum]->children.first->element;
24652 }
24653 if ( chest_inventory )
24654 {
24655 node_t* nextnode = nullptr;
24656 for ( node_t* node = chest_inventory->first; node != NULL__null; node = nextnode )
24657 {
24658 nextnode = node->next;
24659 Item* chestItem = (Item*)node->element;
24660 if ( !chestItem ) { continue; }
24661 if ( chestItem->uid == itemMenuItem )
24662 {
24663 item = chestItem;
24664 break;
24665 }
24666 }
24667 }
24668 }
24669 }
24670
24671 if ( !interactFrame )
24672 {
24673 itemMenuOpen = false;
24674 return;
24675 }
24676
24677
24678 if ( !item || !itemMenuOpen )
24679 {
24680 itemMenuOpen = false;
24681 itemMenuSelected = 0;
24682 itemMenuFromHotbar = false;
24683 interactFrame->setDisabled(true);
24684 return;
24685 }
24686
24687 bool& toggleclick = inputs.getUIInteraction(player.playernum)->toggleclick;
24688 int& itemMenuX = inputs.getUIInteraction(player.playernum)->itemMenuX;
24689 int& itemMenuY = inputs.getUIInteraction(player.playernum)->itemMenuY;
24690 int& itemMenuOffsetDetectionY = inputs.getUIInteraction(player.playernum)->itemMenuOffsetDetectionY;
24691 itemMenuOffsetDetectionY = 0;
24692 const Sint32 mousex = (inputs.getMouse(player.playernum, Inputs::X) / (float)xres) * (float)Frame::virtualScreenX;
24693 const Sint32 mousey = (inputs.getMouse(player.playernum, Inputs::Y) / (float)yres) * (float)Frame::virtualScreenY;
24694
24695 auto highlightImageMid = interactFrame->findImage("interact selected highlight mid");
24696 highlightImageMid->disabled = true;
24697 highlightImageMid->color = hudColors.itemContextMenuOptionSelectedImg;
24698 auto highlightImageLeft = interactFrame->findImage("interact selected highlight left");
24699 highlightImageLeft->disabled = true;
24700 highlightImageLeft->color = hudColors.itemContextMenuOptionSelectedImg;
24701 auto highlightImageRight = interactFrame->findImage("interact selected highlight right");
24702 highlightImageRight->disabled = true;
24703 highlightImageRight->color = hudColors.itemContextMenuOptionSelectedImg;
24704
24705 interactFrame->setDisabled(false);
24706
24707 auto options = getContextMenuOptionsForItem(player.playernum, item);
24708 if ( itemMenuFromHotbar )
24709 {
24710 options.push_back(PROMPT_CLEAR_HOTBAR_SLOT);
24711 std::reverse(options.begin(), options.end());
24712 if ( itemCategory(item) == SPELLBOOK )
24713 {
24714 for ( auto& it : options )
24715 {
24716 if ( it == PROMPT_INTERACT )
24717 {
24718 it = PROMPT_INTERACT_SPELLBOOK_HOTBAR;
24719 }
24720 }
24721 }
24722 }
24723 std::vector<std::pair<Frame::image_t*, Field*>> optionFrames;
24724 auto glyph1 = interactFrame->findImage("glyph 1");
24725 auto option1 = interactFrame->findField("interact option 1");
24726 auto glyph2 = interactFrame->findImage("glyph 2");
24727 auto option2 = interactFrame->findField("interact option 2");
24728 auto glyph3 = interactFrame->findImage("glyph 3");
24729 auto option3 = interactFrame->findField("interact option 3");
24730 auto glyph4 = interactFrame->findImage("glyph 4");
24731 auto option4 = interactFrame->findField("interact option 4");
24732 auto glyph5 = interactFrame->findImage("glyph 5");
24733 auto option5 = interactFrame->findField("interact option 5");
24734 if ( glyph1 && option1 )
24735 {
24736 optionFrames.push_back(std::make_pair(glyph1, option1));
24737 }
24738 if ( glyph2 && option2 )
24739 {
24740 optionFrames.push_back(std::make_pair(glyph2, option2));
24741 }
24742 if ( glyph3 && option3 )
24743 {
24744 optionFrames.push_back(std::make_pair(glyph3, option3));
24745 }
24746 if ( glyph4 && option4 )
24747 {
24748 optionFrames.push_back(std::make_pair(glyph4, option4));
24749 }
24750 if ( glyph5 && option5 )
24751 {
24752 optionFrames.push_back(std::make_pair(glyph5, option5));
24753 }
24754
24755 size_t index = 0;
24756 unsigned int maxWidth = 0;
24757 if ( auto interactText = interactFrame->findField("interact text") )
24758 {
24759 interactText->setColor(hudColors.itemContextMenuHeadingText);
24760 if ( auto textGet = Text::get(interactText->getText(), interactText->getFont(),
24761 makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) )
24762 {
24763 maxWidth = textGet->getWidth();
24764 }
24765 }
24766 int maxHeight = 0;
24767 const int textPaddingX = 8;
24768
24769 for ( auto& optionPair : optionFrames )
24770 {
24771 auto& img = optionPair.first;
24772 auto& txt = optionPair.second;
24773 txt->setColor(hudColors.itemContextMenuOptionText);
24774 if ( index >= options.size() )
24775 {
24776 txt->setDisabled(true);
24777 img->disabled = true;
24778 continue;
24779 }
24780
24781 txt->setDisabled(false);
24782 img->disabled = true;
24783 auto promptType = options[index];
24784
24785 // in-case we want controller support here.
24786 /*switch ( promptType )
24787 {
24788 case PROMPT_EQUIP:
24789 case PROMPT_UNEQUIP:
24790 case PROMPT_SPELL_EQUIP:
24791 img->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuAlt1");
24792 break;
24793 case PROMPT_APPRAISE:
24794 img->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuAlt2");
24795 break;
24796 case PROMPT_DROP:
24797 img->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuCancel");
24798 break;
24799 default:
24800 img->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuConfirm");
24801 break;
24802 }
24803
24804 if ( img->path == "" )
24805 {
24806 img->disabled = true;
24807 }*/
24808
24809 txt->setText(getContextMenuLangEntry(player.playernum, promptType, *item));
24810 if ( auto textGet = Text::get(txt->getText(), txt->getFont(),
24811 makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255)) )
24812 {
24813 maxWidth = std::max(textGet->getWidth(), maxWidth);
24814
24815 SDL_Rect size = txt->getSize();
24816 size.w = textGet->getWidth();
24817 if ( img->disabled )
24818 {
24819 size.x = textPaddingX;
24820 txt->setHJustify(Field::justify_t::CENTER);
24821 }
24822 else
24823 {
24824 size.x = img->pos.x + img->pos.w + textPaddingX;
24825 txt->setHJustify(Field::justify_t::LEFT);
24826 }
24827 txt->setSize(size);
24828 }
24829
24830 maxHeight = std::max(img->pos.y + img->pos.h, maxHeight);
24831 ++index;
24832 }
24833
24834 //maxWidth += 16; -- edit this to add extra left/right padding
24835
24836 const int rightClickProtectBuffer = (right_click_protect ? 0 : 10);
24837
24838 bool alignRight = true;
24839 if ( itemMenuFromHotbar )
24840 {
24841 alignRight = true;
24842 }
24843 else if ( bCompactView )
24844 {
24845 if ( player.paperDoll.isItemOnDoll(*item) )
24846 {
24847 if ( paperDollPanelJustify == PanelJustify_t::PANEL_JUSTIFY_RIGHT )
24848 {
24849 alignRight = false;
24850 }
24851 else
24852 {
24853 alignRight = true;
24854 }
24855 }
24856 else if ( itemCategory(item) == SPELL_CAT )
24857 {
24858 if ( spellPanel.panelJustify == PanelJustify_t::PANEL_JUSTIFY_RIGHT )
24859 {
24860 alignRight = false;
24861 }
24862 else
24863 {
24864 alignRight = true;
24865 }
24866 }
24867 else if ( isItemFromChest(item) )
24868 {
24869 if ( inventoryPanelJustify == PanelJustify_t::PANEL_JUSTIFY_RIGHT )
24870 {
24871 alignRight = true;
24872 }
24873 else
24874 {
24875 alignRight = false;
24876 }
24877 }
24878 else
24879 {
24880 // normal inventory items
24881 if ( inventoryPanelJustify == PanelJustify_t::PANEL_JUSTIFY_RIGHT )
24882 {
24883 alignRight = false;
24884 }
24885 else
24886 {
24887 alignRight = true;
24888 }
24889 }
24890 }
24891
24892 const int textStartX = (glyph1->disabled ? 0 : glyph1->pos.x + glyph1->pos.w) + textPaddingX;
24893 const int frameWidth = maxWidth + textStartX + textPaddingX;
24894
24895 SDL_Rect frameSize = interactFrame->getSize();
24896 frameSize.x = itemMenuX;
24897 frameSize.y = itemMenuY;
24898
24899 // position the frame elements
24900 int interimHeight = 0;
24901 {
24902 auto tl = interactFrame->findImage("interact top left");
24903 auto tmid = interactFrame->findImage("interact top background");
24904 tmid->pos.w = frameWidth - tl->pos.w * 2;
24905 auto tr = interactFrame->findImage("interact top right");
24906 tr->pos.x = tmid->pos.x + tmid->pos.w;
24907
24908 interimHeight = tmid->pos.y + tmid->pos.h;
24909 frameSize.w = tr->pos.x + tr->pos.w;
24910 }
24911 {
24912 const int middleOffsetY = 13;
24913 auto ml = interactFrame->findImage("interact middle left");
24914 ml->pos.h = maxHeight - interimHeight - middleOffsetY;
24915 auto mmid = interactFrame->findImage("interact middle background");
24916 mmid->pos.h = ml->pos.h;
24917 mmid->pos.w = frameWidth - (ml->pos.w) * 2;
24918 mmid->color = hudColors.itemContextMenuOptionImg;
24919 auto mr = interactFrame->findImage("interact middle right");
24920 mr->pos.h = ml->pos.h;
24921 mr->pos.x = mmid->pos.x + mmid->pos.w;
24922
24923 interimHeight = mmid->pos.y + mmid->pos.h;
24924 }
24925 {
24926 auto bl = interactFrame->findImage("interact bottom left");
24927 bl->pos.y = interimHeight;
24928 auto bmid = interactFrame->findImage("interact bottom background");
24929 bmid->pos.y = interimHeight;
24930 bmid->pos.w = frameWidth - bl->pos.w * 2;
24931 auto br = interactFrame->findImage("interact bottom right");
24932 br->pos.y = interimHeight;
24933 br->pos.x = bmid->pos.x + bmid->pos.w;
24934
24935 frameSize.h = br->pos.y + br->pos.h;
24936 }
24937
24938 if ( auto interactText = interactFrame->findField("interact text") )
24939 {
24940 SDL_Rect size = interactText->getSize();
24941 size.x = 0;
24942 size.w = frameSize.w;
24943 interactText->setSize(size);
24944 }
24945
24946 if ( !alignRight )
24947 {
24948 frameSize.x -= frameSize.w;
24949 }
24950
24951 if ( (frameSize.y + frameSize.h) > player.camera_virtualy2() )
24952 {
24953 int yoffset = (frameSize.y + frameSize.h) - player.camera_virtualy2();
24954 frameSize.y -= yoffset;
24955 itemMenuOffsetDetectionY = yoffset;
24956 }
24957 interactFrame->setSize(frameSize);
24958
24959 index = 0;
24960 for ( auto& optionPair : optionFrames )
24961 {
24962 auto& img = optionPair.first;
24963 auto& txt = optionPair.second;
24964
24965 if ( txt->getHJustify() == Field::justify_t::CENTER )
24966 {
24967 SDL_Rect size = txt->getSize();
24968 size.w = maxWidth;
24969 txt->setSize(size);
24970 }
24971
24972 if ( index < options.size() )
24973 {
24974 auto ml = interactFrame->findImage("interact middle left");
24975 SDL_Rect absoluteSize = txt->getAbsoluteSize();
24976 absoluteSize.x -= (4 + ml->pos.w) + (alignRight ? rightClickProtectBuffer : 0);
24977 absoluteSize.w += ((4 + ml->pos.w) * 2 + rightClickProtectBuffer);
24978 absoluteSize.y += 4;
24979 absoluteSize.h -= 4;
24980 absoluteSize.y += itemMenuOffsetDetectionY;
24981 if ( mousex >= absoluteSize.x && mousex < absoluteSize.x + absoluteSize.w
24982 && mousey >= absoluteSize.y && mousey < absoluteSize.y + absoluteSize.h )
24983 {
24984 itemMenuSelected = index;
24985 }
24986 }
24987 ++index;
24988 }
24989
24990 SDL_Rect absoluteSize = interactFrame->getAbsoluteSize();
24991 // right click protect uses exact border, else there is 10 px buffer
24992 absoluteSize.x -= (alignRight ? rightClickProtectBuffer : 0);
24993 absoluteSize.w += rightClickProtectBuffer;
24994 absoluteSize.y += itemMenuOffsetDetectionY;
24995 if ( !(mousex >= absoluteSize.x && mousex < absoluteSize.x + absoluteSize.w
24996 && mousey >= absoluteSize.y && mousey < absoluteSize.y + absoluteSize.h) )
24997 {
24998 itemMenuSelected = -1;
24999 }
25000
25001 if ( itemMenuSelected >= 0 && itemMenuSelected < options.size() )
25002 {
25003 auto& txt = optionFrames[itemMenuSelected].second;
25004 txt->setColor(hudColors.itemContextMenuOptionSelectedText);
25005 SDL_Rect size = txt->getSize();
25006 size.x -= 4;
25007 size.w += 2 * 4;
25008 size.y += 3;
25009 highlightImageMid->pos = size;
25010 highlightImageMid->pos.x += highlightImageLeft->pos.w;
25011 highlightImageMid->pos.w -= 2 * highlightImageLeft->pos.w;
25012 highlightImageMid->pos.h = highlightImageLeft->pos.h;
25013 highlightImageMid->disabled = false;
25014
25015 highlightImageLeft->pos.x = highlightImageMid->pos.x - highlightImageLeft->pos.w;
25016 highlightImageLeft->pos.y = highlightImageMid->pos.y;
25017 highlightImageLeft->disabled = false;
25018 highlightImageRight->pos.x = highlightImageMid->pos.x + highlightImageMid->pos.w;
25019 highlightImageRight->disabled = false;
25020 highlightImageRight->pos.y = highlightImageMid->pos.y;
25021 }
25022
25023 bool activateSelection = false;
25024 if ( !Input::inputs[player.playernum].binary("MenuRightClick") && !toggleclick )
25025 {
25026 activateSelection = true;
25027 }
25028 if ( activateSelection )
25029 {
25030 if ( itemMenuSelected >= 0 && itemMenuSelected < options.size() )
25031 {
25032 if ( options[itemMenuSelected] == ItemContextMenuPrompts::PROMPT_DROP
25033 && player.paperDoll.isItemOnDoll(*item) )
25034 {
25035 // need to unequip
25036 player.inventoryUI.activateItemContextMenuOption(item, ItemContextMenuPrompts::PROMPT_UNEQUIP_FOR_DROP);
25037 player.paperDoll.updateSlots();
25038 if ( player.paperDoll.isItemOnDoll(*item) )
25039 {
25040 // couldn't unequip, no more actions
25041 }
25042 else
25043 {
25044 // successfully unequipped, let's drop it.
25045 bool droppedAll = false;
25046 while ( item && item->count > 1 )
25047 {
25048 droppedAll = dropItem(item, player.playernum);
25049 if ( droppedAll )
25050 {
25051 item = nullptr;
25052 }
25053 }
25054 if ( !droppedAll )
25055 {
25056 dropItem(item, player.playernum);
25057 }
25058 }
25059 }
25060 else
25061 {
25062 activateItemContextMenuOption(item, options[itemMenuSelected]);
25063 }
25064 }
25065 //Close the menu.
25066 itemMenuOpen = false;
25067 itemMenuItem = 0;
25068 interactFrame->setDisabled(true);
25069 }
25070}
25071
25072void Player::Inventory_t::activateItemContextMenuOption(Item* item, ItemContextMenuPrompts prompt)
25073{
25074 if ( !item )
25075 {
25076 return;
25077 }
25078 const int player = this->player.playernum;
25079 bool disableItemUsage = false;
25080 if ( players[player] && players[player]->entity )
25081 {
25082 if ( players[player]->entity->effectShapeshift != NOTHING )
25083 {
25084 // shape shifted, disable some items
25085 if ( !item->usableWhileShapeshifted(stats[player]) )
25086 {
25087 disableItemUsage = true;
25088 }
25089 if ( item->type == FOOD_CREAMPIE && (prompt == PROMPT_UNEQUIP || prompt == PROMPT_EQUIP) )
25090 {
25091 disableItemUsage = true;
25092 }
25093 }
25094 else
25095 {
25096 if ( itemCategory(item) == SPELL_CAT && item->appearance >= 1000 )
25097 {
25098 if ( canUseShapeshiftSpellInCurrentForm(player, *item) != 1 )
25099 {
25100 disableItemUsage = true;
25101 }
25102 }
25103 }
25104 }
25105
25106 if ( client_classes[player] == CLASS_SHAMAN )
25107 {
25108 if ( item->type == SPELL_ITEM && !(playerUnlockedShamanSpell(player, item)) )
25109 {
25110 disableItemUsage = true;
25111 }
25112 }
25113
25114 //PROMPT_EQUIP,
25115 //PROMPT_UNEQUIP,
25116 //PROMPT_SPELL_EQUIP,
25117 //PROMPT_INTERACT,
25118 //PROMPT_EAT,
25119 //PROMPT_CONSUME,
25120//PROMPT_SPELL_QUICKCAST,
25121//PROMPT_INSPECT,
25122//PROMPT_SELL,
25123//PROMPT_BUY,
25124//PROMPT_STORE_CHEST,
25125//PROMPT_RETRIEVE_CHEST,
25126 //PROMPT_APPRAISE,
25127 //PROMPT_DROP
25128
25129 bool sellingItemToShop = false;
25130 if ( players[player]->gui_mode == GUI_MODE_SHOP && itemCategory(item) != SPELL_CAT
25131 && players[player]->shopGUI.bOpen && uidToEntity(shopkeeper[player]) )
25132 {
25133 sellingItemToShop = true;
25134 }
25135
25136 if ( prompt == PROMPT_DROPDOWN )
25137 {
25138 return;
25139 }
25140 if ( prompt == PROMPT_CLEAR_HOTBAR_SLOT )
25141 {
25142 for ( auto& slot : players[player]->hotbar.slots() )
25143 {
25144 if ( slot.item == item->uid )
25145 {
25146 slot.item = 0;
25147 slot.resetLastItem();
25148 }
25149 }
25150 return;
25151 }
25152 if ( prompt == PROMPT_APPRAISE )
25153 {
25154 players[player]->inventoryUI.appraisal.appraiseItem(item);
25155 return;
25156 }
25157 else if ( prompt == PROMPT_DROP )
25158 {
25159 dropItem(item, player);
25160 return;
25161 }
25162 else if ( prompt == PROMPT_SELL )
25163 {
25164 if ( sellingItemToShop )
25165 {
25166 sellItemToShop(player, item);
25167 }
25168 return;
25169 }
25170 else if ( prompt == PROMPT_RETRIEVE_CHEST || prompt == PROMPT_RETRIEVE_CHEST_ALL )
25171 {
25172 bool emptiedSlot = false;
25173 if ( openedChest[player] )
25174 {
25175 if ( prompt == PROMPT_RETRIEVE_CHEST )
25176 {
25177 bool tryAddToInventory = true;
25178 while ( tryAddToInventory )
25179 {
25180 if ( item->count <= 0 )
25181 {
25182 break;
25183 }
25184 int oldItemQty = 0;
25185 int destItemQty = 0;
25186 int oldQty = item->count;
25187 item->count = 1;
25188 bool oldIdentify = item->identified;
25189 if ( skillCapstoneUnlocked(player, PRO_APPRAISAL) )
25190 {
25191 item->identified = true;
25192 }
25193 auto result = getItemStackingBehavior(player, item, nullptr, oldItemQty, destItemQty);
25194 item->identified = oldIdentify;
25195 item->count = oldQty;
25196
25197 int amountToPlace = 1;
25198 switch ( result.resultType )
25199 {
25200 case ITEM_ADDED_ENTIRELY_TO_DESTINATION_STACK:
25201 case ITEM_ADDED_PARTIALLY_TO_DESTINATION_STACK:
25202 {
25203 // operation success, can finish here.
25204 Item* inventoryItem = takeItemFromChest(player, item, amountToPlace, result.itemToStackInto, false);
25205 tryAddToInventory = false;
25206 if ( oldQty == 1 )
25207 {
25208 emptiedSlot = true;
25209 }
25210 break;
25211 }
25212 case ITEM_ADDED_WITHOUT_NEEDING_STACK:
25213 {
25214 // check for inventory space
25215 if ( !bItemInventoryHasFreeSlot() )
25216 {
25217 // no space
25218 tryAddToInventory = false;
25219 messagePlayer(player, MESSAGE_INVENTORY, Language::get(727), item->getName()); // no room
25220 playSoundPlayer(player, 90, 64);
25221 break;
25222 }
25223
25224 // operation success, can finish here.
25225 int amountToPlace = 1;
25226 Item* inventoryItem = takeItemFromChest(player, item, amountToPlace, nullptr, true);
25227 tryAddToInventory = false;
25228 if ( oldQty == 1 )
25229 {
25230 emptiedSlot = true;
25231 }
25232 break;
25233 }
25234 default:
25235 // error out
25236 tryAddToInventory = false;
25237 break;
25238 }
25239 }
25240 }
25241 else if ( prompt == PROMPT_RETRIEVE_CHEST_ALL )
25242 {
25243 bool tryAddToInventory = true;
25244 while ( tryAddToInventory )
25245 {
25246 if ( item->count <= 0 )
25247 {
25248 break;
25249 }
25250 int oldItemQty = 0;
25251 int destItemQty = 0;
25252 bool oldIdentify = item->identified;
25253 if ( skillCapstoneUnlocked(player, PRO_APPRAISAL) )
25254 {
25255 item->identified = true;
25256 }
25257 auto result = getItemStackingBehavior(player, item, nullptr, oldItemQty, destItemQty);
25258 item->identified = oldIdentify;
25259 int amountToPlace = item->count - oldItemQty;
25260 assert(amountToPlace > 0)(static_cast<void> (0));
25261 if ( amountToPlace <= 0 )
25262 {
25263 break;
25264 }
25265 switch ( result.resultType )
25266 {
25267 case ITEM_ADDED_PARTIALLY_TO_DESTINATION_STACK:
25268 {
25269 if ( Item* inventoryItem = takeItemFromChest(player, item, amountToPlace, result.itemToStackInto, false) )
25270 {
25271 // need to do another place operation.
25272 }
25273 else
25274 {
25275 tryAddToInventory = false;
25276 }
25277 break;
25278 }
25279 case ITEM_ADDED_ENTIRELY_TO_DESTINATION_STACK:
25280 {
25281 Item* inventoryItem = takeItemFromChest(player, item, amountToPlace, result.itemToStackInto, false);
25282 if ( oldItemQty > 0 )
25283 {
25284 // more work to do (unusually large stacks exceeding normal limits)
25285 }
25286 else
25287 {
25288 // operation success, can finish here.
25289 tryAddToInventory = false;
25290 if ( oldItemQty == 0 )
25291 {
25292 emptiedSlot = true;
25293 }
25294 }
25295 break;
25296 }
25297 case ITEM_ADDED_WITHOUT_NEEDING_STACK:
25298 {
25299 // check for inventory space
25300 if ( !bItemInventoryHasFreeSlot() )
25301 {
25302 // no space
25303 tryAddToInventory = false;
25304 messagePlayer(player, MESSAGE_INVENTORY, Language::get(727), item->getName()); // no room
25305 playSoundPlayer(player, 90, 64);
25306 break;
25307 }
25308 Item* inventoryItem = takeItemFromChest(player, item, amountToPlace, nullptr, true);
25309 tryAddToInventory = false;
25310 if ( oldItemQty == 0 )
25311 {
25312 emptiedSlot = true;
25313 }
25314 break;
25315 }
25316 default:
25317 // error out
25318 tryAddToInventory = false;
25319 break;
25320 }
25321 }
25322 }
25323 }
25324 if ( !emptiedSlot && (inputs.getUIInteraction(player)->itemMenuOpen || players[player]->inventoryUI.bCompactView) )
25325 {
25326 tooltipDelayTick = ticks + TICKS_PER_SECOND50 / 2;
25327 }
25328 return;
25329 }
25330 else if ( prompt == PROMPT_STORE_CHEST || prompt == PROMPT_STORE_CHEST_ALL )
25331 {
25332 bool emptiedSlot = false;
25333 if ( !disableItemUsage || (disableItemUsage && !players[player]->paperDoll.isItemOnDoll(*item)) )
25334 {
25335 if ( openedChest[player] )
25336 {
25337 if ( prompt == PROMPT_STORE_CHEST )
25338 {
25339 bool tryAddToChest = true;
25340 while ( tryAddToChest )
25341 {
25342 if ( item->count <= 0 )
25343 {
25344 break;
25345 }
25346 int oldItemQty = 0;
25347 int destItemQty = 0;
25348 int oldQty = item->count;
25349 item->count = 1;
25350 auto result = getItemStackingBehaviorIntoChest(player, item, nullptr, oldItemQty, destItemQty);
25351 item->count = oldQty;
25352
25353 int amountToPlace = 1;
25354 switch ( result.resultType )
25355 {
25356 case ITEM_ADDED_ENTIRELY_TO_DESTINATION_STACK:
25357 case ITEM_ADDED_PARTIALLY_TO_DESTINATION_STACK:
25358 {
25359 // operation success, can finish here.
25360 Item* itemInChest = openedChest[player]->addItemToChestFromInventory(player, item, amountToPlace, false, result.itemToStackInto);
25361 tryAddToChest = false;
25362 if ( oldQty == 1 )
25363 {
25364 emptiedSlot = true;
25365 }
25366 break;
25367 }
25368 case ITEM_ADDED_WITHOUT_NEEDING_STACK:
25369 {
25370 // check for chest space
25371 if ( numItemsInChest(player) + 1 > (players[player]->inventoryUI.MAX_CHEST_X * players[player]->inventoryUI.MAX_CHEST_Y) )
25372 {
25373 // no space
25374 tryAddToChest = false;
25375 messagePlayer(player, MESSAGE_INVENTORY, Language::get(4098), item->getName()); // no room
25376 playSoundPlayer(player, 90, 64);
25377 break;
25378 }
25379
25380 // operation success, can finish here.
25381 int amountToPlace = 1;
25382 Item* itemInChest = openedChest[player]->addItemToChestFromInventory(player, item, amountToPlace, true, nullptr);
25383 tryAddToChest = false;
25384 if ( oldQty == 1 )
25385 {
25386 emptiedSlot = true;
25387 }
25388 break;
25389 }
25390 default:
25391 // error out
25392 tryAddToChest = false;
25393 break;
25394 }
25395 }
25396 }
25397 else if ( prompt == PROMPT_STORE_CHEST_ALL )
25398 {
25399 bool tryAddToChest = true;
25400 while ( tryAddToChest )
25401 {
25402 if ( item->count <= 0 )
25403 {
25404 break;
25405 }
25406 int oldItemQty = 0;
25407 int destItemQty = 0;
25408 auto result = getItemStackingBehaviorIntoChest(player, item, nullptr, oldItemQty, destItemQty);
25409 int amountToPlace = item->count - oldItemQty;
25410 assert(amountToPlace > 0)(static_cast<void> (0));
25411 if ( amountToPlace <= 0 )
25412 {
25413 break;
25414 }
25415 switch ( result.resultType )
25416 {
25417 case ITEM_ADDED_PARTIALLY_TO_DESTINATION_STACK:
25418 {
25419 if ( Item* itemInChest = openedChest[player]->addItemToChestFromInventory(player, item, amountToPlace, false, result.itemToStackInto) )
25420 {
25421 // need to do another place operation.
25422 }
25423 else
25424 {
25425 tryAddToChest = false;
25426 }
25427 break;
25428 }
25429 case ITEM_ADDED_ENTIRELY_TO_DESTINATION_STACK:
25430 {
25431 // operation success, can finish here.
25432 Item* itemInChest = openedChest[player]->addItemToChestFromInventory(player, item, amountToPlace, false, result.itemToStackInto);
25433 tryAddToChest = false;
25434 if ( oldItemQty == 0 )
25435 {
25436 emptiedSlot = true;
25437 }
25438 break;
25439 }
25440 case ITEM_ADDED_WITHOUT_NEEDING_STACK:
25441 {
25442 // check for chest space
25443 if ( numItemsInChest(player) + 1 > (players[player]->inventoryUI.MAX_CHEST_X * players[player]->inventoryUI.MAX_CHEST_Y) )
25444 {
25445 // no space
25446 tryAddToChest = false;
25447 messagePlayer(player, MESSAGE_INVENTORY, Language::get(4098), item->getName()); // no room
25448 playSoundPlayer(player, 90, 64);
25449 break;
25450 }
25451 Item* itemInChest = openedChest[player]->addItemToChestFromInventory(player, item, amountToPlace, true, nullptr);
25452 if ( !itemInChest )
25453 {
25454 tryAddToChest = false; // failure adding to chest
25455 }
25456 else if ( oldItemQty > 0 )
25457 {
25458 // more work to do (unusually large stacks exceeding normal limits)
25459 }
25460 else
25461 {
25462 tryAddToChest = false;
25463 if ( oldItemQty == 0 )
25464 {
25465 emptiedSlot = true;
25466 }
25467 }
25468 break;
25469 }
25470 default:
25471 // error out
25472 tryAddToChest = false;
25473 break;
25474 }
25475 }
25476 }
25477 }
25478 }
25479 else
25480 {
25481 messagePlayer(player, MESSAGE_INVENTORY | MESSAGE_HINT | MESSAGE_EQUIPMENT, Language::get(3432)); // unable to use in current form message.
25482 playSoundPlayer(player, 90, 64);
25483 }
25484 if ( !emptiedSlot && (inputs.getUIInteraction(player)->itemMenuOpen || players[player]->inventoryUI.bCompactView) )
25485 {
25486 tooltipDelayTick = ticks + TICKS_PER_SECOND50 / 2;
25487 }
25488 return;
25489 }
25490 else if ( prompt == PROMPT_GRAB )
25491 {
25492 inputs.getUIInteraction(player)->selectedItemFromHotbar = -1;
25493 if ( this->player.inventoryUI.isItemFromChest(item) )
25494 {
25495 inputs.getUIInteraction(player)->selectedItemFromChest = item->uid;
25496 }
25497 inputs.getUIInteraction(player)->selectedItem = item;
25498 playSound(139, 64); // click sound
25499 inputs.getUIInteraction(player)->toggleclick = true;
25500 Input::inputs[player].consumeBinaryToggle("MenuLeftClick");
25501 return;
25502 }
25503 else if ( prompt == PROMPT_EAT )
25504 {
25505 if ( !disableItemUsage )
25506 {
25507 useItem(item, player);
25508 }
25509 return;
25510 }
25511 else if ( prompt == PROMPT_CONSUME || prompt == PROMPT_CONSUME_ALTERNATE )
25512 {
25513 // consume item
25514 if ( multiplayer == CLIENT2 )
25515 {
25516 strcpy((char*)net_packet->data, "FODA");
25517 SDLNet_Write32((Uint32)item->type, &net_packet->data[4])_SDLNet_Write32((Uint32)item->type, &net_packet->data
[4])
;
25518 SDLNet_Write32((Uint32)item->status, &net_packet->data[8])_SDLNet_Write32((Uint32)item->status, &net_packet->
data[8])
;
25519 SDLNet_Write32((Uint32)item->beatitude, &net_packet->data[12])_SDLNet_Write32((Uint32)item->beatitude, &net_packet->
data[12])
;
25520 SDLNet_Write32((Uint32)item->count, &net_packet->data[16])_SDLNet_Write32((Uint32)item->count, &net_packet->data
[16])
;
25521 SDLNet_Write32((Uint32)item->appearance, &net_packet->data[20])_SDLNet_Write32((Uint32)item->appearance, &net_packet->
data[20])
;
25522 net_packet->data[24] = item->identified;
25523 net_packet->data[25] = player;
25524 net_packet->address.host = net_server.host;
25525 net_packet->address.port = net_server.port;
25526 net_packet->len = 26;
25527 sendPacketSafe(net_sock, -1, net_packet, 0);
25528 }
25529 item_FoodAutomaton(item, player);
25530 return;
25531 }
25532 else if ( prompt == PROMPT_INTERACT
25533 || prompt == PROMPT_INTERACT_SPELLBOOK_HOTBAR
25534 || prompt == PROMPT_INSPECT
25535 || prompt == PROMPT_INSPECT_ALTERNATE
25536 || prompt == PROMPT_TINKER )
25537 {
25538 if ( item->type == TOOL_PLAYER_LOOT_BAG )
25539 {
25540 if ( prompt == PROMPT_INTERACT )
25541 {
25542 item_ToolLootBag(item, player);
25543 }
25544 else if ( prompt == PROMPT_INSPECT )
25545 {
25546 useItem(item, player);
25547 }
25548 }
25549 else if ( item->type == TOOL_ALEMBIC )
25550 {
25551 // not experimenting
25552 if ( GenericGUI[player].alchemyGUI.bOpen && GenericGUI[player].alembicItem == item )
25553 {
25554 GenericGUI[player].closeGUI();
25555 }
25556 else if ( !disableItemUsage )
25557 {
25558 if ( item->status > BROKEN )
25559 {
25560 GenericGUI[player].openGUI(GUI_TYPE_ALCHEMY, true, item);
25561 }
25562 else
25563 {
25564 messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(1092), item->getName()); // this is useless!
25565 playSoundPlayer(player, 90, 64);
25566 }
25567 }
25568 else
25569 {
25570 messagePlayer(player, MESSAGE_INVENTORY | MESSAGE_HINT | MESSAGE_EQUIPMENT, Language::get(3432)); // unable to use in current form message.
25571 playSoundPlayer(player, 90, 64);
25572 }
25573 }
25574 else if ( item->type == TOOL_TINKERING_KIT )
25575 {
25576 if ( !disableItemUsage )
25577 {
25578 if ( true /*item->status > BROKEN*/ ) // allow broken tinker kit
25579 {
25580 GenericGUI[player].openGUI(GUI_TYPE_TINKERING, item);
25581 }
25582 else
25583 {
25584 messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(1092), item->getName()); // this is useless!
25585 playSoundPlayer(player, 90, 64);
25586 }
25587 }
25588 else
25589 {
25590 messagePlayer(player, MESSAGE_INVENTORY | MESSAGE_HINT | MESSAGE_EQUIPMENT, Language::get(3432)); // unable to use in current form message.
25591 playSoundPlayer(player, 90, 64);
25592 }
25593 }
25594 else
25595 {
25596 if ( !disableItemUsage && prompt == PROMPT_INTERACT_SPELLBOOK_HOTBAR )
25597 {
25598 if ( itemCategory(item) == SPELLBOOK )
25599 {
25600 players[player]->magic.spellbookUidFromHotbarSlot = item->uid;
25601 }
25602 useItem(item, player);
25603 players[player]->magic.spellbookUidFromHotbarSlot = 0;
25604 }
25605 else if ( !disableItemUsage && prompt == PROMPT_INTERACT )
25606 {
25607 useItem(item, player);
25608 }
25609 else
25610 {
25611 messagePlayer(player, MESSAGE_INVENTORY | MESSAGE_HINT | MESSAGE_EQUIPMENT, Language::get(3432)); // unable to use in current form message.
25612 playSoundPlayer(player, 90, 64);
25613 }
25614 }
25615 return;
25616 }
25617 else if ( prompt == PROMPT_EQUIP
25618 || prompt == PROMPT_UNEQUIP
25619 || prompt == PROMPT_SPELL_EQUIP
25620 || prompt == PROMPT_UNEQUIP_FOR_DROP )
25621 {
25622 if ( isItemEquippableInShieldSlot(item) && cast_animation[player].active_spellbook )
25623 {
25624 return; // don't try to equip shields while casting
25625 }
25626
25627 if ( !disableItemUsage )
25628 {
25629 if ( item->status == BROKEN )
25630 {
25631 messagePlayer(player, MESSAGE_EQUIPMENT, Language::get(1092), item->getName()); // don't try equip broken stuff
25632 playSoundPlayer(player, 90, 64);
25633 return;
25634 }
25635
25636 if ( prompt == PROMPT_EQUIP
25637 || prompt == PROMPT_UNEQUIP
25638 || prompt == PROMPT_UNEQUIP_FOR_DROP )
25639 {
25640 if ( items[item->type].item_slot == ItemEquippableSlot::EQUIPPABLE_IN_SLOT_WEAPON
25641 || itemCategory(item) == SPELLBOOK )
25642 {
25643 if ( prompt == PROMPT_UNEQUIP_FOR_DROP )
25644 {
25645 playerTryEquipItemAndUpdateServer(player, item, false); // don't check inventory space to unequip
25646 }
25647 else
25648 {
25649 playerTryEquipItemAndUpdateServer(player, item, true);
25650 }
25651 return;
25652 }
25653 }
25654
25655 if ( prompt == PROMPT_UNEQUIP_FOR_DROP )
25656 {
25657 useItem(item, player, players[player]->entity, true);
25658 }
25659 else
25660 {
25661 useItem(item, player);
25662 }
25663 return;
25664 }
25665 else
25666 {
25667 if ( client_classes[player] == CLASS_SHAMAN && item->type == SPELL_ITEM )
25668 {
25669 messagePlayer(player, MESSAGE_INVENTORY | MESSAGE_HINT | MESSAGE_EQUIPMENT, Language::get(3488)); // unable to use with current level.
25670 }
25671 else
25672 {
25673 messagePlayer(player, MESSAGE_INVENTORY | MESSAGE_HINT | MESSAGE_EQUIPMENT, Language::get(3432)); // unable to use in current form message.
25674 }
25675 playSoundPlayer(player, 90, 64);
25676 }
25677 return;
25678 }
25679 else if ( prompt == PROMPT_SPELL_QUICKCAST )
25680 {
25681 if ( !disableItemUsage )
25682 {
25683 players[player]->magic.setQuickCastSpellFromInventory(item);
25684 }
25685 else
25686 {
25687 if ( client_classes[player] == CLASS_SHAMAN && item->type == SPELL_ITEM )
25688 {
25689 messagePlayer(player, MESSAGE_INVENTORY | MESSAGE_HINT | MESSAGE_EQUIPMENT, Language::get(3488)); // unable to use with current level.
25690 }
25691 else
25692 {
25693 messagePlayer(player, MESSAGE_INVENTORY | MESSAGE_HINT | MESSAGE_EQUIPMENT, Language::get(3432)); // unable to use in current form message.
25694 }
25695 playSoundPlayer(player, 90, 64);
25696 }
25697 return;
25698 }
25699}
25700
25701void Player::Hotbar_t::updateSelectedSlotAnimation(int destx, int desty, int width, int height, bool usingMouse)
25702{
25703 if ( hotbarFrame )
25704 {
25705 if ( auto selectedSlotCursor = hotbarFrame->findFrame("shootmode selected item cursor") )
25706 {
25707 if ( usingMouse )
25708 {
25709 selectedSlotCursor->setSize(
25710 SDL_Rect{
25711 destx - shootmodeCursor.cursorToSlotOffset,
25712 desty - shootmodeCursor.cursorToSlotOffset,
25713 width + 2 * (shootmodeCursor.cursorToSlotOffset + 1),
25714 height + 2 * (shootmodeCursor.cursorToSlotOffset + 1)
25715 }
25716 );
25717 shootmodeCursor.animateSetpointX = destx;
25718 shootmodeCursor.animateSetpointY = desty;
25719 shootmodeCursor.animateStartX = destx;
25720 shootmodeCursor.animateStartY = desty;
25721 }
25722 else if ( shootmodeCursor.animateSetpointX != destx || shootmodeCursor.animateSetpointY != desty )
25723 {
25724 SDL_Rect size = selectedSlotCursor->getSize();
25725 shootmodeCursor.animateStartX = size.x;
25726 shootmodeCursor.animateStartY = size.y;
25727 size.w = width + 2 * (shootmodeCursor.cursorToSlotOffset + 1);
25728 size.h = height + 2 * (shootmodeCursor.cursorToSlotOffset + 1);
25729 selectedSlotCursor->setSize(size);
25730 shootmodeCursor.animateSetpointX = destx;
25731 shootmodeCursor.animateSetpointY = desty;
25732 shootmodeCursor.animateX = 0.0;
25733 shootmodeCursor.animateY = 0.0;
25734 shootmodeCursor.lastUpdateTick = ticks;
25735 }
25736 }
25737 }
25738}
25739
25740void Player::Inventory_t::updateSelectedItemAnimation()
25741{
25742 if ( !player.isLocalPlayer() )
25743 {
25744 return;
25745 }
25746
25747 if ( frame )
25748 {
25749 if ( auto selectedSlotFrame = frame->findFrame("inventory selected item") )
25750 {
25751 selectedSlotFrame->setDisabled(true);
25752 }
25753 if ( selectedItemCursorFrame )
25754 {
25755 selectedItemCursorFrame->setDisabled(true);
25756 }
25757 }
25758
25759 if ( inputs.getUIInteraction(player.playernum)->selectedItem )
25760 {
25761 const real_t fpsScale = getFPSScale(144.0);
25762 real_t setpointDiffX = fpsScale * std::max(.05, (1.0 - selectedItemAnimate.animateX)) / (5);
25763 real_t setpointDiffY = fpsScale * std::max(.05, (1.0 - selectedItemAnimate.animateY)) / (5);
25764 selectedItemAnimate.animateX += setpointDiffX;
25765 selectedItemAnimate.animateY += setpointDiffY;
25766 selectedItemAnimate.animateX = std::min(1.0, selectedItemAnimate.animateX);
25767 selectedItemAnimate.animateY = std::min(1.0, selectedItemAnimate.animateY);
25768 }
25769 else
25770 {
25771 selectedItemAnimate.animateX = 0.0;
25772 selectedItemAnimate.animateY = 0.0;
25773 }
25774}
25775
25776void Player::Inventory_t::updateInventoryItemTooltip()
25777{
25778 if ( !tooltipFrame || !frame || !titleOnlyTooltipFrame )
25779 {
25780 return;
25781 }
25782
25783 auto& tooltipDisplay = this->itemTooltipDisplay;
25784
25785 if ( static_cast<int>(tooltipFrame->getOpacity()) != tooltipDisplay.opacitySetpoint )
25786 {
25787 const real_t fpsScale = getFPSScale(144.0);
25788 if ( tooltipDisplay.opacitySetpoint == 0 )
25789 {
25790 real_t setpointDiff = fpsScale * std::max(.05, (tooltipDisplay.opacityAnimate)) / (5);
25791 tooltipDisplay.opacityAnimate -= setpointDiff;
25792 tooltipDisplay.opacityAnimate = std::max(0.0, tooltipDisplay.opacityAnimate);
25793 }
25794 else
25795 {
25796 real_t setpointDiff = fpsScale * std::max(.05, (1.0 - tooltipDisplay.opacityAnimate)) / (1);
25797 tooltipDisplay.opacityAnimate += setpointDiff;
25798 tooltipDisplay.opacityAnimate = std::min(1.0, tooltipDisplay.opacityAnimate);
25799 }
25800 tooltipFrame->setOpacity(tooltipDisplay.opacityAnimate * 100);
25801 }
25802 else
25803 {
25804 tooltipFrame->setOpacity(tooltipDisplay.opacitySetpoint);
25805 }
25806
25807 if ( static_cast<int>(titleOnlyTooltipFrame->getOpacity()) != tooltipDisplay.titleOnlyOpacitySetpoint )
25808 {
25809 const real_t fpsScale = getFPSScale(144.0);
25810 if ( tooltipDisplay.titleOnlyOpacitySetpoint == 0 )
25811 {
25812 real_t setpointDiff = fpsScale * std::max(.05, (tooltipDisplay.titleOnlyOpacityAnimate)) / (5);
25813 tooltipDisplay.titleOnlyOpacityAnimate -= setpointDiff;
25814 tooltipDisplay.titleOnlyOpacityAnimate = std::max(0.0, tooltipDisplay.titleOnlyOpacityAnimate);
25815 }
25816 else
25817 {
25818 real_t setpointDiff = fpsScale * std::max(.05, (1.0 - tooltipDisplay.titleOnlyOpacityAnimate)) / (1);
25819 tooltipDisplay.titleOnlyOpacityAnimate += setpointDiff;
25820 tooltipDisplay.titleOnlyOpacityAnimate = std::min(1.0, tooltipDisplay.titleOnlyOpacityAnimate);
25821 }
25822 titleOnlyTooltipFrame->setOpacity(tooltipDisplay.titleOnlyOpacityAnimate * 100);
25823 }
25824 else
25825 {
25826 titleOnlyTooltipFrame->setOpacity(tooltipDisplay.titleOnlyOpacitySetpoint);
25827 }
25828
25829 if ( tooltipPromptFrame )
25830 {
25831 tooltipPromptFrame->setOpacity(tooltipFrame->getOpacity());
25832 }
25833
25834 tooltipDisplay.expandSetpoint = tooltipDisplay.expanded ? 100 : 0;
25835 if ( static_cast<int>(tooltipDisplay.expandCurrent * 100) != tooltipDisplay.expandSetpoint )
25836 {
25837 const real_t fpsScale = getFPSScale(144.0);
25838 if ( tooltipDisplay.expandSetpoint == 0 )
25839 {
25840 //real_t setpointDiff = fpsScale * std::max(.05, (tooltipDisplay.expandAnimate) / 50);
25841 //tooltipDisplay.expandAnimate -= setpointDiff;
25842 tooltipDisplay.expandAnimate -= 2 * fpsScale / 100.0;
25843 tooltipDisplay.expandAnimate = std::max(0.0, tooltipDisplay.expandAnimate);
25844 }
25845 else
25846 {
25847 //real_t setpointDiff = fpsScale * std::max(.05, (1.0 - tooltipDisplay.expandAnimate) / 50);
25848 //tooltipDisplay.expandAnimate += setpointDiff;
25849 tooltipDisplay.expandAnimate += 2 * fpsScale / 100.0;
25850 tooltipDisplay.expandAnimate = std::min(1.0, tooltipDisplay.expandAnimate);
25851 }
25852 double t = tooltipDisplay.expandAnimate;
25853 tooltipDisplay.expandCurrent = t * t * (3.0f - 2.0f * t); // bezier from 0 to width as t (0-1);
25854 }
25855 else
25856 {
25857 tooltipDisplay.expandCurrent = tooltipDisplay.expandSetpoint / 100.0;
25858 }
25859}
25860
25861void Player::Inventory_t::ItemTooltipDisplay_t::updateItem(const int player, Item* newItem)
25862{
25863 if ( newItem && player >= 0 && player < MAXPLAYERS4 && stats[player] )
25864 {
25865 uid = newItem->uid;
25866 type = newItem->type;
25867 status = newItem->status;
25868 beatitude = newItem->beatitude;
25869 count = newItem->count;
25870 appearance = newItem->appearance;
25871 identified = newItem->identified;
25872
25873 if ( players[player]->inventoryUI.appraisal.current_item == uid )
25874 {
25875 wasAppraisalTarget = true;
25876 }
25877 else
25878 {
25879 wasAppraisalTarget = false;
25880 }
25881 if ( stats[player] )
25882 {
25883 playernum = player;
25884 playerLVL = stats[player]->LVL;
25885 playerEXP = stats[player]->EXP;
25886 playerSTR = statGetSTR(stats[player], players[player]->entity);
25887 playerDEX = statGetDEX(stats[player], players[player]->entity);
25888 playerCON = statGetCON(stats[player], players[player]->entity);
25889 playerINT = statGetINT(stats[player], players[player]->entity);
25890 playerPER = statGetPER(stats[player], players[player]->entity);
25891 playerCHR = statGetCHR(stats[player], players[player]->entity);
25892 }
25893 }
25894}
25895
25896bool Player::Inventory_t::ItemTooltipDisplay_t::isItemSameAsCurrent(const int player, Item* newItem)
25897{
25898 if ( newItem && player >= 0 && player < MAXPLAYERS4 && stats[player] )
25899 {
25900 bool appraisingThisItem = false;
25901 if ( players[player]->inventoryUI.appraisal.current_item == uid )
25902 {
25903 appraisingThisItem = true;
25904 }
25905
25906 if ( newItem->uid == uid
25907 && newItem->type == type
25908 && newItem->status == status
25909 && newItem->beatitude == beatitude
25910 && newItem->count == count
25911 && newItem->appearance == appearance
25912 && newItem->identified == identified
25913 && (wasAppraisalTarget == appraisingThisItem)
25914 && playernum == player
25915 && playerLVL == stats[player]->LVL
25916 && playerEXP == stats[player]->EXP
25917 && playerSTR == statGetSTR(stats[player], players[player]->entity)
25918 && playerDEX == statGetDEX(stats[player], players[player]->entity)
25919 && playerCON == statGetCON(stats[player], players[player]->entity)
25920 && playerINT == statGetINT(stats[player], players[player]->entity)
25921 && playerPER == statGetPER(stats[player], players[player]->entity)
25922 && playerCHR == statGetCHR(stats[player], players[player]->entity)
25923 )
25924 {
25925 return true;
25926 }
25927 }
25928 return false;
25929}
25930
25931Player::Inventory_t::ItemTooltipDisplay_t::ItemTooltipDisplay_t()
25932{
25933 uid = 0;
25934 type = WOODEN_SHIELD;
25935 status = BROKEN;
25936 beatitude = 0;
25937 count = 0;
25938 appearance = 0;
25939 identified = false;
25940 wasAppraisalTarget = false;
25941 playernum = -1;
25942 playerLVL = 0;
25943 playerEXP = 0;
25944 playerSTR = 0;
25945 playerDEX = 0;
25946 playerCON = 0;
25947 playerINT = 0;
25948 playerPER = 0;
25949 playerCHR = 0;
25950}
25951
25952void Player::Inventory_t::updateItemContextMenuClickFrame()
25953{
25954 if ( !interactBlockClickFrame )
25955 {
25956 char interactBlockClickName[64] = "";
25957 snprintf(interactBlockClickName, sizeof(interactBlockClickName), "player inventory dropdown block click %d", player.playernum);
25958 if ( interactBlockClickFrame = gameUIFrame[player.playernum]->addFrame(interactBlockClickName) )
25959 {
25960 interactBlockClickFrame->setSize(SDL_Rect{ player.camera_virtualx1(),
25961 player.camera_virtualy1(),
25962 player.camera_virtualWidth(),
25963 player.camera_virtualHeight() });
25964 interactBlockClickFrame->setOwner(player.playernum);
25965 interactBlockClickFrame->setDisabled(true);
25966 interactBlockClickFrame->setHollow(true);
25967 }
25968 }
25969
25970 interactBlockClickFrame->setSize(SDL_Rect{ player.camera_virtualx1(),
25971 player.camera_virtualy1(),
25972 player.camera_virtualWidth(),
25973 player.camera_virtualHeight() });
25974
25975 if ( interactFrame )
25976 {
25977 if ( interactFrame->isDisabled() )
25978 {
25979 interactBlockClickFrame->setDisabled(true);
25980 interactBlockClickFrame->setHollow(true);
25981 }
25982 else
25983 {
25984 interactBlockClickFrame->setDisabled(false);
25985 interactBlockClickFrame->setHollow(false);
25986 }
25987 }
25988}
25989
25990void Player::Inventory_t::updateCursor()
25991{
25992 if ( !frame )
25993 {
25994 return;
25995 }
25996
25997 if ( !player.isLocalPlayer() )
25998 {
25999 return;
26000 }
26001
26002 if ( cursor.queuedModule != Player::GUI_t::MODULE_NONE
26003 && !player.shootmode && !nohud )
26004 {
26005 int cursorWidth = player.inventoryUI.getSlotSize();
26006 int cursorHeight = player.inventoryUI.getSlotSize();
26007 bool moveMouse = false;
26008 auto queuedModule = cursor.queuedModule;
26009 if ( cursor.queuedModule == Player::GUI_t::MODULE_INVENTORY )
26010 {
26011 if ( frame->isDisabled() || player.inventory_mode != INVENTORY_MODE_ITEM )
26012 {
26013 // cancel
26014 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26015 }
26016 else if ( isInteractable )
26017 {
26018 moveMouse = true;
26019 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26020 }
26021 }
26022 else if ( cursor.queuedModule == Player::GUI_t::MODULE_HOTBAR )
26023 {
26024 if ( player.hotbar.hotbarFrame && player.hotbar.hotbarFrame->isDisabled() )
26025 {
26026 // cancel
26027 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26028 }
26029 else if ( player.hotbar.isInteractable )
26030 {
26031 moveMouse = true;
26032 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26033 }
26034 }
26035 else if ( cursor.queuedModule == Player::GUI_t::MODULE_SPELLS )
26036 {
26037 if ( spellFrame->isDisabled() || player.inventory_mode != INVENTORY_MODE_SPELL )
26038 {
26039 // cancel
26040 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26041 }
26042 else if ( spellPanel.isInteractable )
26043 {
26044 moveMouse = true;
26045 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26046 }
26047 }
26048 else if ( cursor.queuedModule == Player::GUI_t::MODULE_CHEST )
26049 {
26050 if ( !chestFrame
26051 || chestFrame->isDisabled()
26052 || player.inventory_mode != INVENTORY_MODE_ITEM
26053 || !openedChest[player.playernum] )
26054 {
26055 // cancel
26056 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26057 }
26058 else if ( chestGUI.isInteractable )
26059 {
26060 moveMouse = true;
26061 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26062 }
26063 }
26064 else if ( cursor.queuedModule == Player::GUI_t::MODULE_SHOP )
26065 {
26066 auto& shopGUI = player.shopGUI;
26067 if ( !shopGUI.shopFrame
26068 || shopGUI.shopFrame->isDisabled()
26069 || player.inventory_mode != INVENTORY_MODE_ITEM
26070 || !shopGUI.bOpen )
26071 {
26072 // cancel
26073 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26074 }
26075 else if ( shopGUI.isInteractable )
26076 {
26077 moveMouse = true;
26078 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26079 }
26080 }
26081 else if ( cursor.queuedModule == Player::GUI_t::MODULE_TINKERING )
26082 {
26083 auto& tinkerGUI = GenericGUI[player.playernum].tinkerGUI;
26084 if ( !tinkerGUI.tinkerGUIHasBeenCreated()
26085 || tinkerGUI.tinkerFrame->isDisabled()
26086 || !tinkerGUI.isConstructMenuActive() )
26087 {
26088 // cancel
26089 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26090 }
26091 else if ( tinkerGUI.isInteractable )
26092 {
26093 moveMouse = true;
26094 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26095 }
26096 if ( cursor.queuedFrameToWarpTo )
26097 {
26098 cursorWidth = cursor.queuedFrameToWarpTo->getSize().w;
26099 cursorHeight = cursor.queuedFrameToWarpTo->getSize().h;
26100 }
26101 }
26102 else if ( cursor.queuedModule == Player::GUI_t::MODULE_ALCHEMY )
26103 {
26104 auto& alchemyGUI = GenericGUI[player.playernum].alchemyGUI;
26105 if ( !alchemyGUI.alchemyGUIHasBeenCreated()
26106 || alchemyGUI.alchFrame->isDisabled() )
26107 {
26108 // cancel
26109 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26110 }
26111 else if ( alchemyGUI.isInteractable )
26112 {
26113 moveMouse = true;
26114 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26115 }
26116 }
26117 else if ( cursor.queuedModule == Player::GUI_t::MODULE_FEATHER )
26118 {
26119 auto& featherGUI = GenericGUI[player.playernum].featherGUI;
26120 if ( !featherGUI.featherGUIHasBeenCreated()
26121 || featherGUI.featherFrame->isDisabled() )
26122 {
26123 // cancel
26124 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26125 }
26126 else if ( featherGUI.isInteractable )
26127 {
26128 moveMouse = true;
26129 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26130 }
26131 if ( cursor.queuedFrameToWarpTo )
26132 {
26133 cursorWidth = cursor.queuedFrameToWarpTo->getSize().w;
26134 cursorHeight = cursor.queuedFrameToWarpTo->getSize().h;
26135 }
26136 }
26137
26138 if ( moveMouse && cursor.queuedFrameToWarpTo )
26139 {
26140 //messagePlayer(0, "Queue warp: %d", queuedModule);
26141 cursor.queuedFrameToWarpTo->warpMouseToFrame(player.playernum, (Inputs::SET_CONTROLLER));
26142 SDL_Rect pos = cursor.queuedFrameToWarpTo->getAbsoluteSize();
26143 pos.x -= player.camera_virtualx1(); // offset any splitscreen camera positioning
26144 pos.y -= player.camera_virtualy1();
26145 player.inventoryUI.updateSelectedSlotAnimation(pos.x, pos.y,
26146 cursorWidth, cursorHeight, false);
26147 cursor.queuedFrameToWarpTo = nullptr;
26148 }
26149 else if ( cursor.queuedFrameToWarpTo && cursor.queuedModule == Player::GUI_t::MODULE_NONE )
26150 {
26151 //messagePlayer(0, "Queue cancel: %d", queuedModule);
26152 }
26153 else if ( cursor.queuedFrameToWarpTo )
26154 {
26155 //selectedItemCursorFrame->setDisabled(false); // show the cursor while we wait
26156 }
26157 }
26158 else
26159 {
26160 cursor.queuedFrameToWarpTo = nullptr;
26161 cursor.queuedModule = Player::GUI_t::MODULE_NONE;
26162 }
26163
26164 if ( auto oldSelectedSlotCursor = frame->findFrame("inventory old item cursor") )
26165 {
26166 if ( auto oldSelectedFrame = frame->findFrame("inventory old selected item") )
26167 {
26168 oldSelectedSlotCursor->setDisabled(oldSelectedFrame->isDisabled());
26169
26170 if ( player.hotbar.hotbarFrame )
26171 {
26172 if ( auto highlight = oldSelectedFrame->findImage("inventory old selected highlight") )
26173 {
26174 highlight->disabled = false;
26175 if ( auto oldHotbarSelectedFrame = player.hotbar.hotbarFrame->findFrame("hotbar old selected item") )
26176 {
26177 if ( !oldHotbarSelectedFrame->isDisabled() )
26178 {
26179 oldSelectedSlotCursor->setDisabled(true);
26180 highlight->disabled = true;
26181 }
26182 }
26183 }
26184 }
26185
26186 if ( !oldSelectedSlotCursor->isDisabled() )
26187 {
26188 SDL_Rect cursorSize = oldSelectedSlotCursor->getSize();
26189 cursorSize.x = (oldSelectedFrame->getSize().x - 1) - cursor.cursorToSlotOffset;
26190 cursorSize.y = (oldSelectedFrame->getSize().y - 1) - cursor.cursorToSlotOffset;
26191 oldSelectedSlotCursor->setSize(cursorSize);
26192
26193 int offset = 8;// ((ticks - cursor.lastUpdateTick) % 50 < 25) ? largeOffset : smallOffset;
26194
26195 Uint8 r, g, b, a;
26196 if ( auto tl = oldSelectedSlotCursor->findImage("inventory old cursor topleft") )
26197 {
26198 tl->pos = SDL_Rect{ offset, offset, tl->pos.w, tl->pos.h };
26199 getColor(tl->color, &r, &g, &b, &a);
26200 a = oldSelectedCursorOpacity;
26201 tl->color = makeColor( r, g, b, a);
26202 }
26203 if ( auto tr = oldSelectedSlotCursor->findImage("inventory old cursor topright") )
26204 {
26205 tr->pos = SDL_Rect{ -offset + cursorSize.w - tr->pos.w, offset, tr->pos.w, tr->pos.h };
26206 tr->color = makeColor( r, g, b, a);
26207 }
26208 if ( auto bl = oldSelectedSlotCursor->findImage("inventory old cursor bottomleft") )
26209 {
26210 bl->pos = SDL_Rect{ offset, -offset + cursorSize.h - bl->pos.h, bl->pos.w, bl->pos.h };
26211 bl->color = makeColor( r, g, b, a);
26212 }
26213 if ( auto br = oldSelectedSlotCursor->findImage("inventory old cursor bottomright") )
26214 {
26215 br->pos = SDL_Rect{ -offset + cursorSize.w - br->pos.w, -offset + cursorSize.h - br->pos.h, br->pos.w, br->pos.h };
26216 br->color = makeColor( r, g, b, a);
26217 }
26218 }
26219 }
26220 }
26221
26222 if ( selectedItemCursorFrame )
26223 {
26224 SDL_Rect cursorSize = selectedItemCursorFrame->getSize();
26225
26226 const int smallOffset = 2;
26227 const int largeOffset = 4;
26228
26229 int offset = ((ticks - cursor.lastUpdateTick) % TICKS_PER_SECOND50 < TICKS_PER_SECOND50 / 2) ? largeOffset : smallOffset;
26230 if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
26231 {
26232 if ( inputs.getUIInteraction(player.playernum)->selectedItem
26233 || inputs.getUIInteraction(player.playernum)->itemMenuOpen )
26234 {
26235 // animate cursor
26236 }
26237 else
26238 {
26239 offset = smallOffset; // don't animate while mouse normal hovering
26240 }
26241 }
26242
26243 Uint8 r, g, b, a;
26244 if ( auto tl = selectedItemCursorFrame->findImage("inventory selected cursor topleft") )
26245 {
26246 tl->pos = SDL_Rect{ offset, offset, tl->pos.w, tl->pos.h };
26247 getColor(tl->color, &r, &g, &b, &a);
26248 a = selectedCursorOpacity;
26249 tl->color = makeColor( r, g, b, a);
26250 }
26251 if ( auto tr = selectedItemCursorFrame->findImage("inventory selected cursor topright") )
26252 {
26253 tr->pos = SDL_Rect{ -offset + cursorSize.w - tr->pos.w, offset, tr->pos.w, tr->pos.h };
26254 tr->color = makeColor( r, g, b, a);
26255 }
26256 if ( auto bl = selectedItemCursorFrame->findImage("inventory selected cursor bottomleft") )
26257 {
26258 bl->pos = SDL_Rect{ offset, -offset + cursorSize.h - bl->pos.h, bl->pos.w, bl->pos.h };
26259 bl->color = makeColor( r, g, b, a);
26260 }
26261 if ( auto br = selectedItemCursorFrame->findImage("inventory selected cursor bottomright") )
26262 {
26263 br->pos = SDL_Rect{ -offset + cursorSize.w - br->pos.w, -offset + cursorSize.h - br->pos.h, br->pos.w, br->pos.h };
26264 br->color = makeColor( r, g, b, a);
26265 }
26266
26267 SDL_Rect currentPos = selectedItemCursorFrame->getSize();
26268 const int offsetPosition = cursor.cursorToSlotOffset;
26269 if ( cursor.animateSetpointX - offsetPosition != currentPos.x
26270 || cursor.animateSetpointY - offsetPosition != currentPos.y )
26271 {
26272 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
26273 real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - cursor.animateX)) / (2.5);
26274 real_t setpointDiffY = fpsScale * std::max(.1, (1.0 - cursor.animateY)) / (2.5);
26275 cursor.animateX += setpointDiffX;
26276 cursor.animateY += setpointDiffY;
26277 cursor.animateX = std::min(1.0, cursor.animateX);
26278 cursor.animateY = std::min(1.0, cursor.animateY);
26279
26280 int destX = cursor.animateSetpointX - cursor.animateStartX - offsetPosition;
26281 int destY = cursor.animateSetpointY - cursor.animateStartY - offsetPosition;
26282
26283 currentPos.x = cursor.animateStartX + destX * cursor.animateX;
26284 currentPos.y = cursor.animateStartY + destY * cursor.animateY;
26285 selectedItemCursorFrame->setSize(currentPos);
26286 //messagePlayer(0, "%.2f | %.2f", inventory_t.selectedSlotAnimateX, setpointDiffX);
26287 }
26288 }
26289}
26290
26291void Player::HUD_t::updateCursorAnimation(int destx, int desty, int width, int height, bool usingMouse)
26292{
26293 if ( cursorFrame )
26294 {
26295 if ( auto hudCursor = cursorFrame->findFrame("hud cursor") )
26296 {
26297 if ( usingMouse )
26298 {
26299 hudCursor->setSize(
26300 SDL_Rect{
26301 destx - cursor.cursorToSlotOffset,
26302 desty - cursor.cursorToSlotOffset,
26303 width + 2 * (cursor.cursorToSlotOffset + 1),
26304 height + 2 * (cursor.cursorToSlotOffset + 1)
26305 }
26306 );
26307 cursor.animateSetpointX = destx;
26308 cursor.animateSetpointY = desty;
26309 cursor.animateSetpointW = width + 2 * (cursor.cursorToSlotOffset + 1);
26310 cursor.animateSetpointH = height + 2 * (cursor.cursorToSlotOffset + 1);
26311
26312 cursor.animateStartX = destx;
26313 cursor.animateStartY = desty;
26314 cursor.animateStartW = width + 2 * (cursor.cursorToSlotOffset + 1);
26315 cursor.animateStartH = height + 2 * (cursor.cursorToSlotOffset + 1);
26316 }
26317 else if ( cursor.animateSetpointX != destx
26318 || cursor.animateSetpointY != desty
26319 || cursor.animateSetpointW != width + 2 * (cursor.cursorToSlotOffset + 1)
26320 || cursor.animateSetpointH != height + 2 * (cursor.cursorToSlotOffset + 1) )
26321 {
26322 SDL_Rect size = hudCursor->getSize();
26323 cursor.animateStartX = size.x;
26324 cursor.animateStartY = size.y;
26325 cursor.animateStartW = size.w;
26326 cursor.animateStartH = size.h;
26327
26328 hudCursor->setSize(size);
26329 cursor.animateSetpointX = destx;
26330 cursor.animateSetpointY = desty;
26331 cursor.animateSetpointW = width + 2 * (cursor.cursorToSlotOffset + 1);
26332 cursor.animateSetpointH = height + 2 * (cursor.cursorToSlotOffset + 1);
26333
26334 cursor.animateX = 0.0;
26335 cursor.animateY = 0.0;
26336 cursor.animateW = 0.0;
26337 cursor.animateH = 0.0;
26338
26339 cursor.lastUpdateTick = ticks;
26340 }
26341 }
26342 }
26343}
26344
26345void Player::HUD_t::updateCursor()
26346{
26347 if ( !cursorFrame )
26348 {
26349 char name[32];
26350 snprintf(name, sizeof(name), "player hud cursor %d", player.playernum);
26351 cursorFrame = gameUIFrame[player.playernum]->addFrame(name);
26352 cursorFrame->setHollow(true);
26353 cursorFrame->setBorder(0);
26354 cursorFrame->setOwner(player.playernum);
26355
26356 auto cursor = cursorFrame->addFrame("hud cursor");
26357 cursor->setHollow(true);
26358 cursor->setSize(SDL_Rect{ 0, 0, 0, 0 });
26359 Uint32 color = makeColor( 255, 255, 255, selectedCursorOpacity);
26360 cursor->addImage(SDL_Rect{ 0, 0, 14, 14 },
26361 color, "*#images/ui/Inventory/Selector_TL.png", "hud cursor topleft");
26362 cursor->addImage(SDL_Rect{ 0, 0, 14, 14 },
26363 color, "*#images/ui/Inventory/Selector_TR.png", "hud cursor topright");
26364 cursor->addImage(SDL_Rect{ 0, 0, 14, 14 },
26365 color, "*#images/ui/Inventory/Selector_BL.png", "hud cursor bottomleft");
26366 cursor->addImage(SDL_Rect{ 0, 0, 14, 14 },
26367 color, "*#images/ui/Inventory/Selector_BR.png", "hud cursor bottomright");
26368 }
26369
26370 cursorFrame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(),
26371 players[player.playernum]->camera_virtualy1(),
26372 players[player.playernum]->camera_virtualWidth(),
26373 players[player.playernum]->camera_virtualHeight() });
26374
26375 if ( !players[player.playernum]->isLocalPlayer()
26376 || players[player.playernum]->shootmode
26377 || players[player.playernum]->GUI.bActiveModuleUsesInventory()
26378 || players[player.playernum]->GUI.bActiveModuleHasNoCursor() )
26379 {
26380 // hide
26381 cursorFrame->setDisabled(true);
26382 }
26383 else
26384 {
26385 cursorFrame->setDisabled(false);
26386 }
26387
26388 if ( auto hudCursor = cursorFrame->findFrame("hud cursor") )
26389 {
26390 SDL_Rect cursorSize = hudCursor->getSize();
26391 const int smallOffset = 2;
26392 const int largeOffset = 4;
26393
26394 int offset = ((ticks - cursor.lastUpdateTick) % TICKS_PER_SECOND50 < TICKS_PER_SECOND50 / 2) ? largeOffset : smallOffset;
26395 if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
26396 {
26397 if ( player.GUI.dropdownMenu.bOpen || player.GUI.activeModule == Player::GUI_t::MODULE_PORTRAIT )
26398 {
26399 // animate cursor
26400 }
26401 else
26402 {
26403 offset = smallOffset; // don't animate while mouse normal hovering
26404 }
26405 }
26406
26407 Uint8 r, g, b, a;
26408 if ( auto tl = hudCursor->findImage("hud cursor topleft") )
26409 {
26410 tl->pos = SDL_Rect{ offset, offset, tl->pos.w, tl->pos.h };
26411 getColor(tl->color, &r, &g, &b, &a);
26412 a = selectedCursorOpacity;
26413 tl->color = makeColor( r, g, b, a);
26414 }
26415 if ( auto tr = hudCursor->findImage("hud cursor topright") )
26416 {
26417 tr->pos = SDL_Rect{ -offset + cursorSize.w - tr->pos.w, offset, tr->pos.w, tr->pos.h };
26418 tr->color = makeColor( r, g, b, a);
26419 }
26420 if ( auto bl = hudCursor->findImage("hud cursor bottomleft") )
26421 {
26422 bl->pos = SDL_Rect{ offset, -offset + cursorSize.h - bl->pos.h, bl->pos.w, bl->pos.h };
26423 bl->color = makeColor( r, g, b, a);
26424 }
26425 if ( auto br = hudCursor->findImage("hud cursor bottomright") )
26426 {
26427 br->pos = SDL_Rect{ -offset + cursorSize.w - br->pos.w, -offset + cursorSize.h - br->pos.h, br->pos.w, br->pos.h };
26428 br->color = makeColor( r, g, b, a);
26429 }
26430
26431 SDL_Rect currentPos = hudCursor->getSize();
26432 const int offsetPosition = cursor.cursorToSlotOffset;
26433 if ( cursor.animateSetpointX - offsetPosition != currentPos.x
26434 || cursor.animateSetpointY - offsetPosition != currentPos.y
26435 || cursor.animateSetpointW != currentPos.w
26436 || cursor.animateSetpointH != currentPos.h )
26437 {
26438 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
26439 real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - cursor.animateX)) / (2.5);
26440 real_t setpointDiffY = fpsScale * std::max(.1, (1.0 - cursor.animateY)) / (2.5);
26441 real_t setpointDiffW = fpsScale * std::max(.1, (1.0 - cursor.animateW)) / (2.5);
26442 real_t setpointDiffH = fpsScale * std::max(.1, (1.0 - cursor.animateH)) / (2.5);
26443 cursor.animateX += setpointDiffX;
26444 cursor.animateY += setpointDiffY;
26445 cursor.animateX = std::min(1.0, cursor.animateX);
26446 cursor.animateY = std::min(1.0, cursor.animateY);
26447 cursor.animateW += setpointDiffW;
26448 cursor.animateH += setpointDiffH;
26449 cursor.animateW = std::min(1.0, cursor.animateW);
26450 cursor.animateH = std::min(1.0, cursor.animateH);
26451
26452 int destX = cursor.animateSetpointX - cursor.animateStartX - offsetPosition;
26453 int destY = cursor.animateSetpointY - cursor.animateStartY - offsetPosition;
26454 int destW = cursor.animateSetpointW - cursor.animateStartW;
26455 int destH = cursor.animateSetpointH - cursor.animateStartH;
26456
26457 currentPos.x = cursor.animateStartX + destX * cursor.animateX;
26458 currentPos.y = cursor.animateStartY + destY * cursor.animateY;
26459 currentPos.w = cursor.animateStartW + destW * cursor.animateW;
26460 currentPos.h = cursor.animateStartH + destH * cursor.animateH;
26461 hudCursor->setSize(currentPos);
26462 }
26463 }
26464}
26465
26466void Player::Hotbar_t::updateCursor()
26467{
26468 if ( !hotbarFrame )
26469 {
26470 return;
26471 }
26472
26473 if ( !player.isLocalPlayer() )
26474 {
26475 return;
26476 }
26477
26478 if ( auto oldSelectedSlotCursor = hotbarFrame->findFrame("hotbar old item cursor") )
26479 {
26480 if ( auto oldSelectedFrame = hotbarFrame->findFrame("hotbar old selected item") )
26481 {
26482 oldSelectedSlotCursor->setDisabled(oldSelectedFrame->isDisabled());
26483
26484 if ( !oldSelectedSlotCursor->isDisabled() )
26485 {
26486 SDL_Rect cursorSize = oldSelectedSlotCursor->getSize();
26487 cursorSize.x = (oldSelectedFrame->getSize().x - 1) - shootmodeCursor.cursorToSlotOffset;
26488 cursorSize.y = (oldSelectedFrame->getSize().y - 1) - shootmodeCursor.cursorToSlotOffset;
26489 oldSelectedSlotCursor->setSize(cursorSize);
26490
26491 int offset = 8;// ((ticks - shootmodeCursor.lastUpdateTick) % TICKS_PER_SECOND < 25) ? largeOffset : smallOffset;
26492
26493 Uint8 r, g, b, a;
26494 if ( auto tl = oldSelectedSlotCursor->findImage("hotbar old cursor topleft") )
26495 {
26496 tl->pos = SDL_Rect{ offset, offset, tl->pos.w, tl->pos.h };
26497 getColor(tl->color, &r, &g, &b, &a);
26498 a = oldSelectedCursorOpacity;
26499 tl->color = makeColor( r, g, b, a);
26500 }
26501 if ( auto tr = oldSelectedSlotCursor->findImage("hotbar old cursor topright") )
26502 {
26503 tr->pos = SDL_Rect{ -offset + cursorSize.w - tr->pos.w, offset, tr->pos.w, tr->pos.h };
26504 tr->color = makeColor( r, g, b, a);
26505 }
26506 if ( auto bl = oldSelectedSlotCursor->findImage("hotbar old cursor bottomleft") )
26507 {
26508 bl->pos = SDL_Rect{ offset, -offset + cursorSize.h - bl->pos.h, bl->pos.w, bl->pos.h };
26509 bl->color = makeColor( r, g, b, a);
26510 }
26511 if ( auto br = oldSelectedSlotCursor->findImage("hotbar old cursor bottomright") )
26512 {
26513 br->pos = SDL_Rect{ -offset + cursorSize.w - br->pos.w, -offset + cursorSize.h - br->pos.h, br->pos.w, br->pos.h };
26514 br->color = makeColor( r, g, b, a);
26515 }
26516 }
26517 }
26518 }
26519
26520 if ( auto selectedSlotCursor = hotbarFrame->findFrame("shootmode selected item cursor") )
26521 {
26522 SDL_Rect cursorSize = selectedSlotCursor->getSize();
26523
26524 const int smallOffset = 2;
26525 const int largeOffset = 4;
26526
26527 int offset = ((ticks - shootmodeCursor.lastUpdateTick) % TICKS_PER_SECOND50 < TICKS_PER_SECOND50 / 2) ? largeOffset : smallOffset;
26528 if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
26529 {
26530 if ( inputs.getUIInteraction(player.playernum)->selectedItem )
26531 {
26532 //offset = largeOffset;
26533 }
26534 else
26535 {
26536 offset = smallOffset;
26537 }
26538 }
26539
26540 Uint8 r, g, b, a;
26541 if ( auto tl = selectedSlotCursor->findImage("shootmode selected cursor topleft") )
26542 {
26543 tl->pos = SDL_Rect{ offset, offset, tl->pos.w, tl->pos.h };
26544 getColor(tl->color, &r, &g, &b, &a);
26545 a = selectedCursorOpacity;
26546 tl->color = makeColor( r, g, b, a);
26547 }
26548 if ( auto tr = selectedSlotCursor->findImage("shootmode selected cursor topright") )
26549 {
26550 tr->pos = SDL_Rect{ -offset + cursorSize.w - tr->pos.w, offset, tr->pos.w, tr->pos.h };
26551 tr->color = makeColor( r, g, b, a);
26552 }
26553 if ( auto bl = selectedSlotCursor->findImage("shootmode selected cursor bottomleft") )
26554 {
26555 bl->pos = SDL_Rect{ offset, -offset + cursorSize.h - bl->pos.h, bl->pos.w, bl->pos.h };
26556 bl->color = makeColor( r, g, b, a);
26557 }
26558 if ( auto br = selectedSlotCursor->findImage("shootmode selected cursor bottomright") )
26559 {
26560 br->pos = SDL_Rect{ -offset + cursorSize.w - br->pos.w, -offset + cursorSize.h - br->pos.h, br->pos.w, br->pos.h };
26561 br->color = makeColor( r, g, b, a);
26562 }
26563
26564 SDL_Rect currentPos = selectedSlotCursor->getSize();
26565 const int offsetPosition = shootmodeCursor.cursorToSlotOffset;
26566 if ( shootmodeCursor.animateSetpointX - offsetPosition != currentPos.x
26567 || shootmodeCursor.animateSetpointY - offsetPosition != currentPos.y )
26568 {
26569 auto& cursor = shootmodeCursor;
26570 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
26571 real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - cursor.animateX)) / (2.5);
26572 real_t setpointDiffY = fpsScale * std::max(.1, (1.0 - cursor.animateY)) / (2.5);
26573 cursor.animateX += setpointDiffX;
26574 cursor.animateY += setpointDiffY;
26575 cursor.animateX = std::min(1.0, cursor.animateX);
26576 cursor.animateY = std::min(1.0, cursor.animateY);
26577
26578 int destX = cursor.animateSetpointX - cursor.animateStartX - offsetPosition;
26579 int destY = cursor.animateSetpointY - cursor.animateStartY - offsetPosition;
26580
26581 currentPos.x = cursor.animateStartX + destX * cursor.animateX;
26582 currentPos.y = cursor.animateStartY + destY * cursor.animateY;
26583 selectedSlotCursor->setSize(currentPos);
26584 //messagePlayer(0, "%.2f | %.2f", inventory_t.selectedSlotAnimateX, setpointDiffX);
26585 }
26586 }
26587}
26588
26589void Player::Inventory_t::processInventory()
26590{
26591 if ( !player.characterSheet.sheetFrame )
26592 {
26593 player.characterSheet.createCharacterSheet();
26594 }
26595 if ( !frame )
26596 {
26597 createPlayerInventory(player.playernum);
26598 }
26599 if ( !tooltipFrame )
26600 {
26601 createInventoryTooltipFrame(player.playernum);
26602 }
26603
26604 frame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(),
26605 players[player.playernum]->camera_virtualy1(),
26606 players[player.playernum]->camera_virtualWidth(),
26607 players[player.playernum]->camera_virtualHeight()});
26608
26609 bool tooltipWasDisabled = tooltipFrame->isDisabled();
26610
26611 updateInventory();
26612
26613 player.shopGUI.updateShop();
26614
26615 if ( tooltipWasDisabled && !tooltipFrame->isDisabled() )
26616 {
26617 tooltipFrame->setOpacity(0.0);
26618 }
26619}
26620
26621void Player::HUD_t::resetBars()
26622{
26623 xpInfo.cycleProcessedOnTick = 0;
26624 xpInfo.cycleStatus = XPInfo_t::CYCLE_NONE;
26625 xpInfo.cycleTicks = 0;
26626 xpInfo.fade = 1.0;
26627 xpInfo.fadeIn = true;
26628 if ( xpFrame )
26629 {
26630 xpBar.animateSetpoint = std::min(100, stats[player.playernum]->EXP) * 10;
26631 xpBar.animateValue = xpBar.animateSetpoint;
26632 xpBar.animatePreviousSetpoint = xpBar.animateSetpoint;
26633 xpBar.animateState = ANIMATE_NONE;
26634 xpBar.xpLevelups = 0;
26635 }
26636 if ( hpFrame )
26637 {
26638 HPBar.animateSetpoint = stats[player.playernum]->HP;
26639 HPBar.animateValue = HPBar.animateSetpoint;
26640 HPBar.animateValue2 = HPBar.animateSetpoint;
26641 HPBar.animatePreviousSetpoint = HPBar.animateSetpoint;
26642 HPBar.animateState = ANIMATE_NONE;
26643 HPBar.flashAnimState = -1;
26644 HPBar.flashTicks = 0;
26645 }
26646 if ( mpFrame )
26647 {
26648 MPBar.animateSetpoint = stats[player.playernum]->MP;
26649 MPBar.animateValue = MPBar.animateSetpoint;
26650 MPBar.animateValue2 = MPBar.animateSetpoint;
26651 MPBar.animatePreviousSetpoint = MPBar.animateSetpoint;
26652 MPBar.animateState = ANIMATE_NONE;
26653 MPBar.flashAnimState = -1;
26654 MPBar.flashTicks = 0;
26655 }
26656}
26657
26658
26659void Player::HUD_t::updateMinimapPrompts()
26660{
26661 if ( !mapPromptFrame )
26662 {
26663 return;
26664 }
26665
26666 bool showPrompts = false;
26667 if ( gamePaused )
26668 {
26669 showPrompts = false;
26670 }
26671 else if ( ::minimapFrame && !::minimapFrame->isInvisible() )
26672 {
26673 if ( player.hud.offsetHUDAboveHotbarHeight > 0 || !player.shootmode )
26674 {
26675 showPrompts = false;
26676 }
26677 else
26678 {
26679 showPrompts = true;
26680 }
26681 }
26682 else if ( this->minimapFrame && !this->minimapFrame->isInvisible() )
26683 {
26684 if ( player.hud.offsetHUDAboveHotbarHeight > 0 )
26685 {
26686 showPrompts = false;
26687 }
26688 else
26689 {
26690 showPrompts = true;
26691 }
26692 }
26693
26694 if ( !showPrompts )
26695 {
26696 mapPromptFrame->setDisabled(true);
26697 return;
26698 }
26699
26700 int maxHeight1 = 0;
26701 int maxHeight2 = 0;
26702 auto imgPromptFrame = mapPromptFrame->findFrame("img prompt frame");
26703 auto scalePrompt = imgPromptFrame->findImage("scale prompt");
26704 scalePrompt->path = Input::inputs[player.playernum].getGlyphPathForBinding("Minimap Scale");
26705 scalePrompt->disabled = true;
26706 /*if ( auto imgGet = Image::get(scalePrompt->path.c_str()) )
26707 {
26708 scalePrompt->pos.w = imgGet->getWidth();
26709 scalePrompt->pos.h = imgGet->getHeight();
26710 maxHeight1 = std::max(maxHeight1, scalePrompt->pos.h);
26711 scalePrompt->disabled = false;
26712 }*/
26713 auto scaleImg = imgPromptFrame->findImage("scale img");
26714 scaleImg->disabled = true;
26715 /*if ( !scalePrompt->disabled )
26716 {
26717 if ( auto imgGet = Image::get(scaleImg->path.c_str()) )
26718 {
26719 scaleImg->pos.w = imgGet->getWidth();
26720 scaleImg->pos.h = imgGet->getHeight();
26721 maxHeight1 = std::max(maxHeight1, scaleImg->pos.h);
26722 scaleImg->disabled = false;
26723 }
26724 }*/
26725 auto expandPrompt = imgPromptFrame->findImage("expand prompt");
26726 expandPrompt->disabled = true;
26727 expandPrompt->path = Input::inputs[player.playernum].getGlyphPathForBinding("Toggle Minimap");
26728 if ( auto imgGet = Image::get(expandPrompt->path.c_str()) )
26729 {
26730 expandPrompt->pos.w = imgGet->getWidth();
26731 expandPrompt->pos.h = imgGet->getHeight();
26732 maxHeight2 = std::max(maxHeight2, expandPrompt->pos.h);
26733 expandPrompt->disabled = false;
26734 }
26735 auto expandImg = imgPromptFrame->findImage("expand img");
26736 expandImg->disabled = true;
26737 if ( !expandPrompt->disabled )
26738 {
26739 if ( auto imgGet = Image::get(expandImg->path.c_str()) )
26740 {
26741 expandImg->pos.w = imgGet->getWidth();
26742 expandImg->pos.h = imgGet->getHeight();
26743 maxHeight2 = std::max(maxHeight2, expandImg->pos.h);
26744 expandImg->disabled = false;
26745 }
26746 }
26747 std::vector<Frame::image_t*> imgs;
26748 if ( !expandPrompt->disabled )
26749 {
26750 imgs.push_back(expandPrompt);
26751 imgs.push_back(expandImg);
26752 }
26753 if ( !scalePrompt->disabled )
26754 {
26755 imgs.push_back(scalePrompt);
26756 imgs.push_back(scaleImg);
26757 }
26758 if ( imgs.empty() )
26759 {
26760 mapPromptFrame->setDisabled(true);
26761 return;
26762 }
26763
26764 bool alignHorizontal = !*cvar_minimap_prompt_vertical;
26765 int imgX = -2;
26766 int index = -1;
26767 int lowestY = 0;
26768 int rightX = 0;
26769
26770 for ( auto img : imgs )
26771 {
26772 ++index;
26773 if ( img->disabled )
26774 {
26775 continue;
26776 }
26777 if ( alignHorizontal )
26778 {
26779 imgX += 2;
26780 if ( index == 2 )
26781 {
26782 imgX += 2;
26783 }
26784 img->pos.x = imgX;
26785 img->pos.y = (std::max(maxHeight1, maxHeight2) / 2) - img->pos.h / 2;
26786 if ( img->pos.y % 2 == 1 )
26787 {
26788 ++img->pos.y;
26789 }
26790 imgX += (img->pos.w);
26791 lowestY = std::max(lowestY, img->pos.y + img->pos.h);
26792 rightX = std::max(rightX, img->pos.x + img->pos.h);
26793 }
26794 else
26795 {
26796 imgX += 2;
26797 if ( index == 2 )
26798 {
26799 imgX = 0;
26800 }
26801 img->pos.x = imgX;
26802 if ( img == scaleImg || img == scalePrompt )
26803 {
26804 img->pos.y = (maxHeight1 / 2) - img->pos.h / 2;
26805 }
26806 else
26807 {
26808 img->pos.y = maxHeight1 + 2 + (maxHeight2 / 2) - img->pos.h / 2;
26809 }
26810 if ( img->pos.y % 2 == 1 )
26811 {
26812 ++img->pos.y;
26813 }
26814 imgX += (img->pos.w);
26815 lowestY = std::max(lowestY, img->pos.y + img->pos.h);
26816 rightX = std::max(rightX, img->pos.x + img->pos.h);
26817 }
26818 }
26819 if ( !player.minimap.bExpandPromptEnabled )
26820 {
26821 expandPrompt->disabled = true;
26822 }
26823 if ( !player.minimap.bScalePromptEnabled )
26824 {
26825 scalePrompt->disabled = true;
26826 }
26827
26828 mapPromptFrame->setDisabled(false);
26829
26830 auto promptBg = mapPromptFrame->findImage("prompt bg");
26831 promptBg->disabled = true;
26832
26833 SDL_Rect pos = mapPromptFrame->getSize();
26834 if ( hudFrame )
26835 {
26836 if ( alignHorizontal )
26837 {
26838 if ( auto imgGet = Image::get(promptBg->path.c_str()) )
26839 {
26840 if ( imgs.size() > 2 )
26841 {
26842 promptBg->path = "*#images/ui/MapAndLog/HUD_MapPromptBase_00.png";
26843 }
26844 else
26845 {
26846 promptBg->path = "*#images/ui/MapAndLog/HUD_MapPromptBase_Short_00.png";
26847 }
26848 promptBg->disabled = false;
26849 promptBg->pos.w = imgGet->getWidth();
26850 promptBg->pos.h = imgGet->getHeight();
26851 pos.w = promptBg->pos.w;
26852 pos.h = std::max(promptBg->pos.h, lowestY);
26853
26854 SDL_Rect imgPromptFramePos = imgPromptFrame->getSize();
26855 imgPromptFramePos.w = rightX;
26856 imgPromptFramePos.h = lowestY;
26857 imgPromptFramePos.x = pos.w / 2 - imgPromptFramePos.w / 2;
26858 if ( imgPromptFramePos.x % 2 == 1 )
26859 {
26860 ++imgPromptFramePos.x;
26861 }
26862 imgPromptFramePos.y = (pos.h / 2) - imgPromptFramePos.h / 2;
26863 if ( imgPromptFramePos.y % 2 == 1 )
26864 {
26865 ++imgPromptFramePos.y;
26866 }
26867 imgPromptFrame->setSize(imgPromptFramePos);
26868 }
26869 pos.x = hudFrame->getSize().w - pos.w;
26870 if ( player.bUseCompactGUIHeight() || player.bUseCompactGUIWidth() )
26871 {
26872 if ( imgs.size() > 2 )
26873 {
26874 pos.x += 16;
26875 }
26876 else
26877 {
26878 pos.x += 16;
26879 }
26880 }
26881 pos.y = hudFrame->getSize().h - pos.h - player.hud.offsetHUDAboveHotbarHeight;
26882 }
26883 else
26884 {
26885
26886 SDL_Rect imgPromptFramePos = imgPromptFrame->getSize();
26887 imgPromptFramePos.x = 0;
26888 imgPromptFramePos.y = 0;
26889 imgPromptFramePos.w = rightX + 8;
26890 imgPromptFramePos.h = lowestY;
26891 imgPromptFrame->setSize(imgPromptFramePos);
26892
26893 pos.w = imgPromptFramePos.w;
26894 pos.h = imgPromptFramePos.h;
26895 pos.x = hudFrame->getSize().w - pos.w;
26896 pos.y = hudFrame->getSize().h - player.minimap.minimapPos.h - pos.h;
26897 }
26898 }
26899 mapPromptFrame->setSize(pos);
26900}
26901
26902static ConsoleVariable<bool> cvar_showmapseed("/showmapseed", false);
26903
26904void Player::HUD_t::updateGameTimer()
26905{
26906 if ( !gameTimerFrame )
26907 {
26908 return;
26909 }
26910
26911 bool overrideGameTimerSetting = false;
26912 if ( splitscreen && !(player.bUseCompactGUIHeight() && player.bUseCompactGUIWidth())
26913 && !player.shootmode && !FollowerMenu[player.playernum].followerMenuIsOpen()
26914 && !CalloutMenu[player.playernum].calloutMenuIsOpen()
26915 && player.gui_mode != GUI_MODE_NONE )
26916 {
26917 if ( compactLayoutMode == COMPACT_LAYOUT_INVENTORY || player.bUseCompactGUIWidth() )
26918 {
26919 overrideGameTimerSetting = true;
26920 }
26921 }
26922
26923 if ( overrideGameTimerSetting )
26924 {
26925 // display on hud in splitscreen !shootmode
26926 }
26927 else
26928 {
26929 if ( !player.shootmode || (!player.characterSheet.showGameTimerAlways || splitscreen) )
26930 {
26931 gameTimerFrame->setDisabled(true);
26932 return;
26933 }
26934 }
26935
26936 gameTimerFrame->setDisabled(false);
26937 SDL_Rect pos = gameTimerFrame->getSize();
26938 pos.x = hudFrame->getSize().w - pos.w - 6;
26939
26940 Field* timerText = gameTimerFrame->findField("timer txt");
26941 SDL_Rect timerTextPos = timerText->getSize();
26942 timerTextPos.x = 72;
26943 timerTextPos.y = 1;
26944 timerText->setSize(timerTextPos);
26945
26946 char buf[64] = "";
26947 Uint32 sec = (completionTime / TICKS_PER_SECOND50) % 60;
26948 Uint32 min = ((completionTime / TICKS_PER_SECOND50) / 60) % 60;
26949 Uint32 hour = (((completionTime / TICKS_PER_SECOND50) / 60) / 60) % 24;
26950 Uint32 day = ((completionTime / TICKS_PER_SECOND50) / 60) / 60 / 24;
26951
26952 auto seed = gameTimerFrame->findField("seed txt");
26953 seed->setDisabled(true);
26954 if ( *cvar_showmapseed )
26955 {
26956 seed->setDisabled(false);
26957 pos.h = 44;
26958 char seedbuf[32];
26959 snprintf(seedbuf, sizeof(seedbuf), "%u", mapseed);
26960 seed->setSize(SDL_Rect{ 0, 21, pos.w, 24 });
26961 seed->setText(seedbuf);
26962 }
26963 else
26964 {
26965 pos.h = 24;
26966 }
26967
26968 pos.y = gameTimerFrame->getParent()->getSize().h - pos.h;
26969
26970 if ( player.hud.mapPromptFrame && !player.hud.mapPromptFrame->isDisabled() )
26971 {
26972 SDL_Rect mapPromptPos = player.hud.mapPromptFrame->getSize();
26973 pos.y = mapPromptPos.y + mapPromptPos.h / 2 - pos.h / 2;
26974 pos.x = mapPromptPos.x - pos.w - 4;
26975 if ( splitscreen && (player.bUseCompactGUIHeight() || player.bUseCompactGUIWidth()) )
26976 {
26977 pos.x += 16;
26978 }
26979 }
26980
26981 if ( day > 0 )
26982 {
26983 snprintf(buf, sizeof(buf), "%02d:%02d:%02d:%02d", day, hour, min, sec);
26984 pos.x -= 24;
26985 }
26986 else
26987 {
26988 snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour, min, sec);
26989 }
26990
26991 gameTimerFrame->setSize(pos);
26992 timerText->setText(buf);
26993}
26994
26995void Player::HUD_t::updateXPBar()
26996{
26997 if ( !xpFrame )
1
Assuming field 'xpFrame' is non-null
2
Taking false branch
26998 {
26999 return;
27000 }
27001
27002 bool bCompactWidth = false;
27003 bool bCompactHeight = player.bUseCompactGUIHeight();
27004 if ( player.bUseCompactGUIWidth()
3
Assuming the condition is false
6
Taking false branch
27005 || (keystatus[SDLK_t] && enableDebugKeys)
4
Assuming the condition is false
27006 || (!player.bUseCompactGUIWidth() && player.bUseCompactGUIHeight() && *MainMenu::clipped_splitscreen) )
5
Assuming the condition is false
27007 {
27008 bCompactWidth = true;
27009 }
27010
27011 xpFrame->setInheritParentFrameOpacity(false);
27012 xpFrame->setOpacity(100.0);
27013 SDL_Rect pos = xpFrame->getSize();
27014 pos.w = XP_FRAME_WIDTH + (bCompactWidth
6.1
'bCompactWidth' is false
? xpbarCompactOffsetWidth : xpbarOffsetWidth);
7
'?' condition is false
27015 pos.x = hudFrame->getSize().w / 2 - pos.w / 2;
27016 if ( bCompactWidth
7.1
'bCompactWidth' is false
|| bCompactHeight )
8
Assuming 'bCompactHeight' is false
27017 {
27018 pos.y = 0 + ((bCompactWidth || bCompactHeight) ? xpbarCompactOffsetY : xpbarOffsetY);
27019 }
27020 else
27021 {
27022 pos.y = 8 + ((bCompactWidth
8.1
'bCompactWidth' is false
|| bCompactHeight
8.2
'bCompactHeight' is false
) ? xpbarCompactOffsetY : xpbarOffsetY);
9
'?' condition is false
27023 }
27024
27025 bool tempHideXP = false;
27026 bool fadeOut = false;
27027 if ( !levelUpAnimation[player.playernum].lvlUps.empty() && !levelUpAnimation[player.playernum].lvlUps[0].titleFinishAnim )
27028 {
27029 tempHideXP = true;
27030 }
27031 else if ( (player.gui_mode == GUI_MODE_FOLLOWERMENU || player.gui_mode == GUI_MODE_CALLOUT
10
Assuming 'GUI_MODE_FOLLOWERMENU' is not equal to field 'gui_mode'
11
Assuming 'GUI_MODE_CALLOUT' is not equal to field 'gui_mode'
27032 || player.minimap.mapWindow || player.messageZone.logWindow)
12
Assuming field 'mapWindow' is null
13
Assuming field 'logWindow' is null
27033 && player.bUseCompactGUIHeight() )
27034 {
27035 tempHideXP = true;
27036 }
27037 else if ( player.bUseCompactGUIHeight() && player.shootmode && !skillUpAnimation[player.playernum].skillUps.empty()
14
Assuming the condition is false
27038 && !(!levelUpAnimation[player.playernum].lvlUps.empty() && levelUpAnimation[player.playernum].lvlUps[0].titleFinishAnim) )
27039 {
27040 // hide xp in compact height, if skillup active and level up isn't playing
27041 tempHideXP = true;
27042 fadeOut = true;
27043 }
27044 if ( tempHideXP
14.1
'tempHideXP' is false
)
15
Taking false branch
27045 {
27046 if ( fadeOut )
27047 {
27048 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
27049 real_t setpointDiff = fpsScale * std::max(.1, (1.0 - animHideXP)) / 2.5;
27050 animHideXP += setpointDiff;
27051 animHideXP = std::min(1.0, animHideXP);
27052 }
27053 else
27054 {
27055 animHideXP = 1.0;
27056 }
27057 }
27058 else
27059 {
27060 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
27061 real_t setpointDiff = fpsScale * std::max(.1, (animHideXP)) / 2.5;
27062 animHideXP -= setpointDiff;
27063 animHideXP = std::max(0.0, animHideXP);
27064 }
27065 //pos.y -= animHideXP * (pos.y + pos.h);
27066 xpFrame->setOpacity((1.0 - animHideXP) * 100.0);
27067 xpFrame->setSize(pos);
27068
27069 auto xpBg = xpFrame->findImage("xp img base");
27070 auto xpProgress = xpFrame->findImage("xp img progress");
27071 xpProgress->disabled = true;
27072 auto xpProgressEndCap = xpFrame->findImage("xp img progress endcap");
27073 auto endCapRight = xpFrame->findImage("xp img endcap right");
27074 xpBg->pos.w = pos.w;
27075 if ( auto xpBgFlair = xpFrame->findImage("xp img base flair") )
16
Assuming 'xpBgFlair' is null
17
Taking false branch
27076 {
27077 xpBgFlair->pos.w = pos.w - 8;
27078 }
27079 endCapRight->pos.x = pos.w - endCapRight->pos.w;
27080
27081 xpBar.animateSetpoint = std::min(100, stats[player.playernum]->EXP);
27082 xpBar.maxValue = 1000.0;
27083
27084 if ( xpBar.animateState == ANIMATE_LEVELUP_RISING )
18
Assuming field 'animateState' is not equal to ANIMATE_LEVELUP_RISING
19
Taking false branch
27085 {
27086 xpBar.animateTicks = ticks;
27087
27088 real_t fpsScale = getFPSScale(144.0);
27089 xpBar.animateValue += fpsScale * (10); // constant speed
27090 xpBar.animateValue = std::min(xpBar.maxValue, xpBar.animateValue);
27091 if ( xpBar.animateValue == xpBar.maxValue )
27092 {
27093 xpBar.animateState = ANIMATE_LEVELUP_FALLING;
27094 }
27095 }
27096 else if ( xpBar.animateState == ANIMATE_LEVELUP_FALLING )
20
Assuming field 'animateState' is not equal to ANIMATE_LEVELUP_FALLING
21
Taking false branch
27097 {
27098 if ( ticks - xpBar.animateTicks > TICKS_PER_SECOND50 * 2 )
27099 {
27100 int decrement = 40;
27101 double scaledDecrement = (decrement * (getFPSScale(144.0)));
27102 xpBar.animateValue -= scaledDecrement;
27103 if ( xpBar.animateValue <= 0 )
27104 {
27105 xpBar.animateValue = 0.0;
27106 xpBar.xpLevelups = 0; // disable looping maybe one day use it
27107 if ( xpBar.xpLevelups > 1 )
27108 {
27109 --xpBar.xpLevelups;
27110 xpBar.animateState = ANIMATE_LEVELUP_RISING;
27111 }
27112 else
27113 {
27114 xpBar.xpLevelups = 0;
27115 xpBar.animateState = ANIMATE_NONE;
27116 }
27117 }
27118 }
27119 }
27120 else
27121 {
27122 int increment = 3;
27123 double scaledIncrement = (increment * (getFPSScale(144.0)));
27124 if ( xpBar.animateValue < xpBar.animateSetpoint * 10 )
22
Assuming the condition is false
23
Taking false branch
27125 {
27126 //real_t diff = std::max(.1, (xpBar.animateSetpoint * 10 - xpBar.animateValue) / 200.0); // 0.1-5 value
27127 //if ( xpBar.animateSetpoint * 10 >= xpBar.maxValue )
27128 //{
27129 // diff = 5;
27130 //}
27131 //scaledIncrement *= 0.2 * pow(diff, 2) + .5;
27132 //xpBar.animateValue = std::min(xpBar.animateSetpoint * 10.0, xpBar.animateValue + scaledIncrement);
27133
27134 real_t setpointDiff = std::max(10.0, xpBar.animateSetpoint * 10.0 - xpBar.animateValue);
27135 real_t fpsScale = getFPSScale(144.0);
27136 xpBar.animateValue += fpsScale * (setpointDiff / 100.0); // reach it in x intervals, scaled to FPS
27137 xpBar.animateValue = std::min(static_cast<real_t>(xpBar.animateSetpoint * 10.0), xpBar.animateValue);
27138 //messagePlayer(0, "%.2f | %.2f", diff, scaledIncrement);
27139 }
27140 else if ( xpBar.animateValue > xpBar.animateSetpoint * 10 )
24
Assuming the condition is false
25
Taking false branch
27141 {
27142 xpBar.animateValue = xpBar.animateSetpoint * 10; // invalid state, reset progress bar
27143 }
27144 //else if ( xpBar.animateValue > xpBar.animateSetpoint * 10 )
27145 //{
27146 // real_t fpsScale = getFPSScale(144.0);
27147 // xpBar.animateValue += fpsScale * (10); // constant speed
27148 // xpBar.animateValue = std::min(xpBar.maxValue, xpBar.animateValue);
27149 //}
27150 //else
27151 //{
27152 // xpBar.animateTicks = ticks;
27153 //}
27154
27155 xpBar.animateTicks = ticks;
27156
27157 //if ( xpBar.animateValue == xpBar.maxValue )
27158 //{
27159 // xpBar.animateState = ANIMATE_LEVELUP;
27160 //}
27161 }
27162
27163 //if ( xpInfo.cycleProcessedOnTick != ticks )
27164 //{
27165 // ++xpInfo.cycleTicks;
27166 //}
27167
27168 //if ( bCompactWidth )
27169 //{
27170 // if ( xpInfo.cycleStatus == XPInfo_t::CYCLE_NONE )
27171 // {
27172 // xpInfo.cycleStatus = XPInfo_t::CYCLE_LVL;
27173 // }
27174
27175 // if ( !xpInfo.fadeIn )
27176 // {
27177 // xpInfo.cycleTicks = 0;
27178 // }
27179
27180 // if ( xpInfo.cycleTicks > 0 &&
27181 // (xpInfo.cycleTicks >= TICKS_PER_SECOND * 5 || xpBar.animateState != ANIMATE_NONE) )
27182 // {
27183 // xpInfo.fadeIn = false;
27184 // }
27185
27186 // if ( !xpInfo.fadeIn )
27187 // {
27188 // const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
27189 // real_t setpointDiffX = fpsScale * std::max(.1, (xpInfo.fade)) / (2.5);
27190 // xpInfo.fade -= setpointDiffX;
27191 // xpInfo.fade = std::max(0.0, xpInfo.fade);
27192 // if ( xpInfo.fade <= 0.0 )
27193 // {
27194 // if ( xpInfo.cycleStatus == XPInfo_t::CYCLE_LVL )
27195 // {
27196 // xpInfo.cycleStatus = XPInfo_t::CYCLE_XP;
27197 // }
27198 // else
27199 // {
27200 // xpInfo.cycleStatus = XPInfo_t::CYCLE_LVL;
27201 // }
27202 // xpInfo.fadeIn = true;
27203 // }
27204 // }
27205 // else
27206 // {
27207 // const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
27208 // real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - xpInfo.fade)) / (2.5);
27209 // xpInfo.fade += setpointDiffX;
27210 // xpInfo.fade = std::min(1.0, xpInfo.fade);
27211 // }
27212
27213 // messagePlayer(0, MESSAGE_DEBUG, "%f", xpInfo.fade);
27214 //}
27215 //else
27216 {
27217 xpInfo.cycleStatus = XPInfo_t::CYCLE_NONE;
27218 xpInfo.cycleTicks = 0;
27219 xpInfo.fade = 1.0;
27220 }
27221
27222 {
27223 char playerXPText[16];
27224
27225 auto xpTextStatic = xpFrame->findField("xp text static");
27226 SDL_Rect xpTextStaticPos = xpTextStatic->getSize();
27227
27228 int offsetx = pos.w / 2 - xpTextStaticPos.w - 24;
27229 if ( bCompactWidth
25.1
'bCompactWidth' is false
)
26
Taking false branch
27230 {
27231 xpTextStatic->setDisabled(true);
27232 snprintf(playerXPText, sizeof(playerXPText), "%.f %s", xpBar.animateValue / 10, Language::get(6107));
27233 }
27234 else
27235 {
27236 snprintf(playerXPText, sizeof(playerXPText), "%.f", xpBar.animateValue / 10);
27237 xpTextStatic->setDisabled(false);
27238 }
27239
27240 xpTextStaticPos.x = pos.w / 2 - 4 + offsetx;
27241 xpTextStatic->setSize(xpTextStaticPos);
27242 auto xpText = xpFrame->findField("xp text current");
27243 xpText->setText(playerXPText);
27244 SDL_Rect xpTextPos = xpText->getSize();
27245 if ( bCompactWidth
26.1
'bCompactWidth' is false
)
27
Taking false branch
27246 {
27247 xpTextPos.x = pos.w - 32 - xpTextPos.w;
27248 }
27249 else
27250 {
27251 xpTextPos.x = pos.w / 2 - (4 * 2) - xpTextPos.w + offsetx;
27252 }
27253 xpText->setSize(xpTextPos);
27254
27255 if ( xpInfo.cycleStatus
27.1
Field 'cycleStatus' is equal to CYCLE_NONE
!= XPInfo_t::CYCLE_NONE )
28
Taking false branch
27256 {
27257 Uint8 r, g, b, a;
27258 getColor(xpText->getColor(), &r, &g, &b, &a);
27259 if ( xpInfo.cycleStatus == XPInfo_t::CYCLE_XP )
27260 {
27261 a = 255 * xpInfo.fade;
27262 }
27263 else
27264 {
27265 a = 0;
27266 }
27267 xpText->setColor(makeColor(r, g, b, a));
27268 xpTextStatic->setColor(makeColor(r, g, b, a));
27269 }
27270 }
27271
27272 {
27273 auto textLevel = xpFrame->findField("xp text lvl");
27274 textLevel->setDisabled(false);
27275 char textLevelBuf[64];
27276 snprintf(textLevelBuf, sizeof(textLevelBuf), "LVL %d", stats[player.playernum]->LVL);
27277 textLevel->setText(textLevelBuf);
27278 SDL_Rect textPos = textLevel->getSize();
27279
27280 if ( auto textGet = textLevel->getTextObject() )
29
Assuming 'textGet' is null
30
Taking false branch
27281 {
27282 textPos.w = textGet->getWidth();
27283 }
27284 textPos.x = 32;
27285 textLevel->setSize(textPos);
27286
27287 {
27288 if ( xpInfo.cycleStatus
30.1
Field 'cycleStatus' is equal to CYCLE_NONE
!= XPInfo_t::CYCLE_NONE )
31
Taking false branch
27289 {
27290 Uint8 r, g, b, a;
27291 getColor(textLevel->getColor(), &r, &g, &b, &a);
27292 if ( xpInfo.cycleStatus == XPInfo_t::CYCLE_LVL )
27293 {
27294 a = 255 * xpInfo.fade;
27295 }
27296 else
27297 {
27298 a = 0;
27299 }
27300 textLevel->setColor(makeColor(r, g, b, a));
27301 }
27302 }
27303
27304 auto textClass = xpFrame->findField("xp text class");
27305 textClass->setDisabled(true);
27306 if ( !bCompactWidth
31.1
'bCompactWidth' is false
)
32
Taking true branch
27307 {
27308 std::string classname = playerClassLangEntry(client_classes[player.playernum], player.playernum);
27309 if ( !classname.empty() )
33
Assuming the condition is false
34
Taking false branch
27310 {
27311 capitalizeString(classname);
27312 textClass->setText(classname.c_str());
27313 textClass->setDisabled(false);
27314 }
27315 if ( true )
35
Taking true branch
27316 {
27317 textClass->setColor(0xFFFFFFFF);
27318 }
27319 else if ( client_classes[player.playernum] >= CLASS_CONJURER && client_classes[player.playernum] <= CLASS_BREWER )
27320 {
27321 textClass->setColor(hudColors.characterDLC1ClassText);
27322 }
27323 else if ( client_classes[player.playernum] >= CLASS_MACHINIST && client_classes[player.playernum] <= CLASS_HUNTER )
27324 {
27325 textClass->setColor(hudColors.characterDLC2ClassText);
27326 }
27327 else
27328 {
27329 textClass->setColor(hudColors.characterBaseClassText);
27330 }
27331 {
27332 SDL_Rect textPos2 = textClass->getSize();
27333 if ( auto textGet = textClass->getTextObject() )
36
Assuming 'textGet' is null
37
Taking false branch
27334 {
27335 textPos2.w = textGet->getWidth();
27336 }
27337 textPos2.x = textPos.x + textPos.w + 8;
27338 textClass->setSize(textPos2);
27339
27340 if ( xpInfo.cycleStatus
37.1
Field 'cycleStatus' is equal to CYCLE_NONE
!= XPInfo_t::CYCLE_NONE )
38
Taking false branch
27341 {
27342 Uint8 r, g, b, a;
27343 getColor(textClass->getColor(), &r, &g, &b, &a);
27344 if ( xpInfo.cycleStatus == XPInfo_t::CYCLE_LVL )
27345 {
27346 a = 255 * xpInfo.fade;
27347 }
27348 else
27349 {
27350 a = 0;
27351 }
27352 textClass->setColor(makeColor(r, g, b, a));
27353 }
27354 }
27355 }
27356 }
27357
27358
27359 real_t percent = xpBar.animateValue / 1000.0;
27360 xpProgress->pos.w = std::max(1, static_cast<int>((xpBg->pos.w - xpProgressEndCap->pos.w) * percent));
27361 if ( !xpProgress->disabled
38.1
Field 'disabled' is true
)
39
Taking false branch
27362 {
27363 xpProgressEndCap->pos.x = xpProgress->pos.x + xpProgress->pos.w;
27364 }
27365
27366 auto xpProgressClipFrame = xpFrame->findFrame("xp progress clipping frame");
27367 SDL_Rect clipFramePos = xpProgressClipFrame->getSize();
27368 clipFramePos.w = std::max(1, static_cast<int>((xpBg->pos.w ) * percent));
27369 if ( xpProgress->disabled
39.1
Field 'disabled' is true
)
40
Taking true branch
27370 {
27371 xpProgressEndCap->pos.x = clipFramePos.x + clipFramePos.w;
27372
27373 bool animate = true;
27374 if ( ticks % 5 == 0 )
41
Assuming the condition is true
42
Taking true branch
27375 {
27376 bool moving = (xpBar.animateSetpoint * 10.0 - xpBar.animateValue != 0);
27377 std::string playerStr = "00";
27378 switch ( player.playernum )
43
Control jumps to 'case 0:' at line 27380
27379 {
27380 case 0:
27381 default:
27382 break;
44
Execution continues on line 27393
27383 case 1:
27384 playerStr = "01";
27385 break;
27386 case 2:
27387 playerStr = "02";
27388 break;
27389 case 3:
27390 playerStr = "03";
27391 break;
27392 }
27393 int xpPathNum = player.playernum;
27394 if ( player.playernum >= playerXPCapPaths.size() )
45
Assuming the condition is true
46
Taking true branch
27395 {
27396 xpPathNum = 0;
27397 }
27398 if ( moving && xpProgressEndCap->path == playerXPCapPaths[xpPathNum][0] )
47
Assuming 'moving' is false
27399 {
27400 xpProgressEndCap->path = playerXPCapPaths[xpPathNum][1];
27401 }
27402 else if ( xpProgressEndCap->path == playerXPCapPaths[xpPathNum][1] )
48
Wrong iterator dereferenced
27403 {
27404 xpProgressEndCap->path = playerXPCapPaths[xpPathNum][2];
27405 }
27406 else if ( xpProgressEndCap->path == playerXPCapPaths[xpPathNum][2] )
27407 {
27408 xpProgressEndCap->path = playerXPCapPaths[xpPathNum][3];
27409 }
27410 else if ( xpProgressEndCap->path == playerXPCapPaths[xpPathNum][3] )
27411 {
27412 xpProgressEndCap->path = playerXPCapPaths[xpPathNum][4];
27413 }
27414 else if ( xpProgressEndCap->path == playerXPCapPaths[player.playernum][4] )
27415 {
27416 xpProgressEndCap->path = playerXPCapPaths[xpPathNum][0];
27417 }
27418 }
27419 }
27420 xpProgressClipFrame->setSize(clipFramePos);
27421 auto xpProgressClipFrameImg = xpProgressClipFrame->findImage("xp img progress clipped");
27422 xpProgressClipFrameImg->pos.x = -(xpProgressClipFrameImg->pos.w - pos.w) / 2;
27423}
27424
27425bool EnemyHPDamageBarHandler::bEnemyBarSimpleBlit = false;
27426static ConsoleVariable<bool> cvar_enemybar_simple_blit("/enemybar_simple_blit", true);
27427
27428// to nest deep maps and suppress visual studio warnings
27429struct enemybarMapLowDurationTick_k {
27430 std::map<bool, SDL_Surface*> m;
27431 ~enemybarMapLowDurationTick_k()
27432 {
27433 if ( EnemyHPDamageBarHandler::bEnemyBarSimpleBlit )
27434 {
27435 for ( auto& entry : m )
27436 {
27437 if ( entry.second )
27438 {
27439 SDL_FreeSurface(entry.second);
27440 entry.second = nullptr;
27441 }
27442 }
27443 }
27444 };
27445};
27446struct enemybarEffectMapFx2_lowDuration_k {
27447 std::map<Uint32, enemybarMapLowDurationTick_k> m;
27448};
27449struct enemybarEffectMapFx1_lowDuration_k {
27450 std::map<Uint32, enemybarEffectMapFx2_lowDuration_k> m;
27451};
27452struct enemybarEffectMapFx2_k {
27453 std::map<Uint32, enemybarEffectMapFx1_lowDuration_k> m;
27454};
27455struct enemybarEffectMapFx1_k {
27456 std::map<Uint32, enemybarEffectMapFx2_k> m;
27457};
27458enemybarEffectMapFx1_k enemyBarEffectMap;
27459SDL_Surface* enemyBarEffectMapExists(Uint32 fx1, Uint32 fx2, Uint32 fx_lowDuration1, Uint32 fx_lowDuration2, bool lowDurationTicks)
27460{
27461 if ( enemyBarEffectMap.m.find(fx1) != enemyBarEffectMap.m.end() )
27462 {
27463 auto& m1 = enemyBarEffectMap.m[fx1];
27464 if ( m1.m.find(fx2) != m1.m.end() )
27465 {
27466 auto& m2 = m1.m[fx2];
27467 if ( m2.m.find(fx_lowDuration1) != m2.m.end() )
27468 {
27469 auto& m3 = m2.m[fx_lowDuration1];
27470 if ( m3.m.find(fx_lowDuration2) != m3.m.end() )
27471 {
27472 auto& m4 = m3.m[fx_lowDuration2];
27473 if ( m4.m.find(lowDurationTicks) != m4.m.end() )
27474 {
27475 return m4.m[lowDurationTicks];
27476 }
27477 }
27478 }
27479 }
27480 }
27481 return nullptr;
27482}
27483void enemyBarEffectMapInsert(Uint32 fx1, Uint32 fx2, Uint32 fx_lowDuration1, Uint32 fx_lowDuration2, bool lowDurationTicks,
27484 SDL_Surface* surf)
27485{
27486 enemyBarEffectMap.m[fx1].m[fx2].m[fx_lowDuration1].m[fx_lowDuration2].m[lowDurationTicks] = surf;
27487}
27488
27489// to nest deep maps and suppress visual studio warnings
27490struct enemybarMapFx2_lowDuration_k {
27491 std::map<Uint32, SDL_Surface*> m;
27492 ~enemybarMapFx2_lowDuration_k()
27493 {
27494 if ( EnemyHPDamageBarHandler::bEnemyBarSimpleBlit )
27495 {
27496 for ( auto& entry : m )
27497 {
27498 if ( entry.second )
27499 {
27500 SDL_FreeSurface(entry.second);
27501 entry.second = nullptr;
27502 }
27503 }
27504 }
27505 };
27506};
27507struct enemybarMapFx1_lowDuration_k {
27508 std::map<Uint32, enemybarMapFx2_lowDuration_k> m;
27509};
27510struct enemybarMapFx2_k {
27511 std::map<Uint32, enemybarMapFx1_lowDuration_k> m;
27512};
27513struct enemybarMapFx1_k {
27514 std::map<Uint32, enemybarMapFx2_k> m;
27515};
27516struct enemybarMapProgress_k {
27517 std::map<Uint32, enemybarMapFx1_k> m;
27518};
27519struct enemybarMapTotalSize_k {
27520 std::map<Uint32, enemybarMapProgress_k> m;
27521};
27522struct enemybarMapName_k {
27523 std::map<std::string, enemybarMapTotalSize_k> m;
27524};
27525enemybarMapName_k enemyBarMap;
27526SDL_Surface* enemyBarMapExists(std::string name, int baseWidth, int baseHeight,
27527 int progressWidth, int damageWidth,
27528 Uint32 fx1, Uint32 fx2, Uint32 fx_lowDuration1, Uint32 fx_lowDuration2)
27529{
27530 if ( enemyBarMap.m.find(name) != enemyBarMap.m.end() )
27531 {
27532 auto& m1 = enemyBarMap.m[name];
27533 Uint32 totalSizeKey = baseWidth & 0xFFFF;
27534 totalSizeKey |= (baseHeight << 16) & 0xFF0000;
27535 totalSizeKey |= (((ticks % 25) >= 12) << 24) & 0xFF000000;
27536 if ( m1.m.find(totalSizeKey) != m1.m.end() )
27537 {
27538 auto& m2 = m1.m[totalSizeKey];
27539 Uint32 progressDamageKey = progressWidth & 0xFFFF;
27540 progressDamageKey |= (damageWidth << 16) & 0xFFFF0000;
27541 if ( m2.m.find(progressDamageKey) != m2.m.end() )
27542 {
27543 auto& m3 = m2.m[progressDamageKey];
27544 if ( m3.m.find(fx1) != m3.m.end() )
27545 {
27546 auto& m4 = m3.m[fx1];
27547 if ( m4.m.find(fx2) != m4.m.end() )
27548 {
27549 auto& m5 = m4.m[fx2];
27550 if ( m5.m.find(fx_lowDuration1) != m5.m.end() )
27551 {
27552 auto& m6 = m5.m[fx_lowDuration1];
27553 if ( m6.m.find(fx_lowDuration2) != m6.m.end() )
27554 {
27555 return m6.m[fx_lowDuration2];
27556 }
27557 }
27558 }
27559 }
27560 }
27561 }
27562 }
27563 return nullptr;
27564}
27565static void enemyBarMapInsert(std::string name, int baseWidth, int baseHeight, int progressWidth, int damageWidth,
27566 Uint32 statusfx1, Uint32 statusfx2,
27567 Uint32 statusfx_lowDuration1, Uint32 statusfx_lowDuration2,
27568 SDL_Surface* surf)
27569{
27570 Uint32 totalSizeKey = baseWidth & 0xFFFF;
27571 totalSizeKey |= (baseHeight << 16) & 0xFF0000;
27572 totalSizeKey |= (((ticks % 25) >= 12) << 24) & 0xFF000000;
27573 Uint32 progressDamageKey = progressWidth & 0xFFFF;
27574 progressDamageKey |= ((damageWidth & 0xFFFF) << 16);
27575 enemyBarMap.m[name].m[totalSizeKey].m[progressDamageKey].m[statusfx1].m[statusfx2].m[statusfx_lowDuration1].m[statusfx_lowDuration2] = surf;
27576}
27577
27578SDL_Surface* EnemyHPDamageBarHandler::EnemyHPDetails::blitEnemyBar(const int player, SDL_Surface* statusEffectSprite)
27579{
27580 Frame* frame = players[player]->hud.enemyBarFrame;
27581 if ( !frame || !players[player]->isLocalPlayer() )
27582 {
27583 return nullptr;
27584 }
27585
27586 auto baseBg = frame->findImage("base img");
27587 auto baseEndCap = frame->findImage("base img endcap");
27588 real_t frameOpacity = frame->getOpacity() / 100.0;
27589 frameOpacity = 1.0;
27590 /*if ( EnemyHPDamageBarHandler::bEnemyBarSimpleBlit )
27591 {
27592 frameOpacity = 1.0;
27593 }*/
27594
27595 auto foregroundFrame = frame->findFrame("bar progress frame");
27596 auto hpProgress = foregroundFrame->findImage("progress img");
27597 auto hpProgressEndcap = foregroundFrame->findImage("progress img endcap");
27598
27599 auto dmgFrame = frame->findFrame("bar dmg frame");
27600 auto dmgProgress = dmgFrame->findImage("dmg img");
27601 auto dmgEndCap = dmgFrame->findImage("dmg img endcap");
27602
27603 auto nameField = frame->findField("enemy name txt");
27604
27605 auto skullFrame = frame->findFrame("skull frame");
27606 int totalWidth = baseBg->pos.x + baseBg->pos.w + baseEndCap->pos.w;
27607 int totalHeight = frame->getSize().h;
27608 int statusEffectOffsetY = 0;
27609
27610 if ( statusEffectSprite )
27611 {
27612 statusEffectOffsetY = statusEffectSprite->h;
27613 totalHeight += statusEffectOffsetY;
27614 }
27615
27616 SDL_Surface* hashSurf = nullptr;
27617 if ( EnemyHPDamageBarHandler::bEnemyBarSimpleBlit )
27618 {
27619 hashSurf = enemyBarMapExists(nameField->getText(),
27620 totalWidth, totalHeight,
27621 dmgProgress->pos.w, hpProgress->pos.w,
27622 enemy_statusEffects1,
27623 enemy_statusEffects2,
27624 enemy_statusEffectsLowDuration1,
27625 enemy_statusEffectsLowDuration2);
27626 if ( !hashSurf )
27627 {
27628 //messagePlayer(0, MESSAGE_DEBUG, "Hash for enemy bar not found!");
27629 }
27630 else
27631 {
27632 return hashSurf;
27633 }
27634 }
27635
27636 SDL_Surface* sprite = SDL_CreateRGBSurface(0, totalWidth, totalHeight, 32,
27637 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
27638 for ( auto& img : frame->getImages() )
27639 {
27640 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
27641 Uint8 r, g, b, a;
27642 getColor(img->color, &r, &g, &b, &a);
27643 SDL_SetSurfaceAlphaMod(srcSurf, a * frameOpacity);
27644 SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
27645 SDL_Rect pos = img->pos;
27646 pos.y += statusEffectOffsetY;
27647 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, sprite, &pos);
27648 }
27649 for ( auto& img : dmgFrame->getImages() )
27650 {
27651 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
27652 Uint8 r, g, b, a;
27653 getColor(img->color, &r, &g, &b, &a);
27654 if ( EnemyHPDamageBarHandler::bEnemyBarSimpleBlit && a < 255 ) { continue; }
27655 SDL_SetSurfaceAlphaMod(srcSurf, a * frameOpacity);
27656 //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
27657 SDL_Rect pos = img->pos;
27658 pos.x += dmgFrame->getSize().x;
27659 pos.y += dmgFrame->getSize().y;
27660 pos.y += statusEffectOffsetY;
27661 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, sprite, &pos);
27662 }
27663 for ( auto& img : foregroundFrame->getImages() )
27664 {
27665 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
27666 Uint8 r, g, b, a;
27667 getColor(img->color, &r, &g, &b, &a);
27668 SDL_SetSurfaceAlphaMod(srcSurf, a * frameOpacity);
27669 //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
27670 SDL_Rect pos = img->pos;
27671 pos.x += foregroundFrame->getSize().x;
27672 pos.y += foregroundFrame->getSize().y;
27673 pos.y += statusEffectOffsetY;
27674 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, sprite, &pos);
27675 }
27676 for ( auto& img : skullFrame->getImages() )
27677 {
27678 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
27679 Uint8 r, g, b, a;
27680 getColor(img->color, &r, &g, &b, &a);
27681 if ( EnemyHPDamageBarHandler::bEnemyBarSimpleBlit && a < 255 ) { continue; }
27682 SDL_SetSurfaceAlphaMod(srcSurf, a * frameOpacity);
27683 SDL_Rect pos = img->pos;
27684 pos.x += skullFrame->getSize().x;
27685 pos.y += skullFrame->getSize().y;
27686 pos.y += statusEffectOffsetY;
27687 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, sprite, &pos);
27688 }
27689 for ( auto& txt : frame->getFields() )
27690 {
27691 auto textGet = Text::get(txt->getText(), txt->getFont(),
27692 makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255));
27693 SDL_Surface* txtSurf = const_cast<SDL_Surface*>(textGet->getSurf());
27694 SDL_Rect pos;
27695 pos.w = textGet->getWidth();
27696 pos.h = textGet->getHeight();
27697 pos.x = sprite->w / 2 - pos.w / 2;
27698 pos.y = frame->getSize().h / 2 - pos.h / 2;
27699 pos.y += statusEffectOffsetY;
27700 Uint8 r, g, b, a;
27701 getColor(txt->getColor(), &r, &g, &b, &a);
27702 SDL_SetSurfaceAlphaMod(txtSurf, a * frameOpacity);
27703 SDL_BlitSurfaceSDL_UpperBlit(txtSurf, nullptr, sprite, &pos);
27704 }
27705 if ( statusEffectSprite )
27706 {
27707 int status_x = (sprite->w / 2) - (statusEffectSprite->w / 2);
27708 SDL_Rect pos{ status_x, 0, statusEffectSprite->w, statusEffectSprite->h};
27709 SDL_BlitSurfaceSDL_UpperBlit(statusEffectSprite, nullptr, sprite, &pos);
27710 }
27711
27712 if ( !hashSurf && EnemyHPDamageBarHandler::bEnemyBarSimpleBlit )
27713 {
27714 enemyBarMapInsert(nameField->getText(),
27715 totalWidth, totalHeight,
27716 dmgProgress->pos.w, hpProgress->pos.w,
27717 enemy_statusEffects1,
27718 enemy_statusEffects2,
27719 enemy_statusEffectsLowDuration1,
27720 enemy_statusEffectsLowDuration2,
27721 sprite);
27722 }
27723 return sprite;
27724}
27725
27726SDL_Surface* EnemyHPDamageBarHandler::EnemyHPDetails::blitEnemyBarStatusEffects(const int player)
27727{
27728 Entity* entity = uidToEntity(enemy_uid);
27729 if ( entity && (entity->behavior != &actPlayer && entity->behavior != &actMonster) )
27730 {
27731 return nullptr;
27732 }
27733 Frame* frame = players[player]->hud.enemyBarFrame;
27734 if ( !frame || !players[player]->isLocalPlayer() )
27735 {
27736 return nullptr;
27737 }
27738 if ( enemy_statusEffects1 == 0 && enemy_statusEffects2 == 0 )
27739 {
27740 return nullptr;
27741 }
27742 //auto baseBg = frame->findImage("base img");
27743 //auto baseEndCap = frame->findImage("base img endcap");
27744 //const int maxWidth = baseBg->pos.x + baseBg->pos.w + baseEndCap->pos.w;
27745 const int iconHeight = 32;
27746 const int iconWidth = 32;
27747
27748 SDL_Surface* hashSurf = nullptr;
27749 if ( EnemyHPDamageBarHandler::bEnemyBarSimpleBlit )
27750 {
27751 hashSurf = enemyBarEffectMapExists(
27752 enemy_statusEffects1,
27753 enemy_statusEffects2,
27754 enemy_statusEffectsLowDuration1,
27755 enemy_statusEffectsLowDuration2,
27756 (ticks % 25) >= 12);
27757 if ( !hashSurf )
27758 {
27759 //messagePlayer(0, MESSAGE_DEBUG, "Hash for enemy effects not found!");
27760 }
27761 else
27762 {
27763 return hashSurf;
27764 }
27765 }
27766
27767 int playernum = -1;
27768 if ( entity && entity->behavior == &actPlayer )
27769 {
27770 playernum = entity->skill[2];
27771 }
27772
27773 int currentX = 0; // (maxWidth / 2);
27774 bool anyStatusEffect = false;
27775
27776 std::vector<std::pair<SDL_Surface*, bool>> statusEffectIcons;
27777 if ( enemy_statusEffects1 != 0 )
27778 {
27779 for ( int i = 0; i < 32; ++i )
27780 {
27781 if ( (enemy_statusEffects1 & (1 << i)) != 0 )
27782 {
27783 if ( StatusEffectQueue_t::StatusEffectDefinitions_t::effectDefinitionExists(i) )
27784 {
27785 int variation = -1;
27786 SDL_Surface* srcSurf = nullptr;
27787 if ( i == EFF_SHAPESHIFT )
27788 {
27789 if ( entity && entity->behavior == &actPlayer )
27790 {
27791 switch ( entity->effectShapeshift )
27792 {
27793 case RAT:
27794 variation = 0;
27795 break;
27796 case SPIDER:
27797 variation = 1;
27798 break;
27799 case TROLL:
27800 variation = 2;
27801 break;
27802 case CREATURE_IMP:
27803 variation = 3;
27804 break;
27805 default:
27806 break;
27807 }
27808 }
27809 }
27810 auto& definition = StatusEffectQueue_t::StatusEffectDefinitions_t::getEffect(i);
27811 if ( !definition.neverDisplay )
27812 {
27813 std::string imgPath;
27814 if ( i == EFF_SHAPESHIFT && variation == -1 )
27815 {
27816 imgPath = "";
27817 }
27818 else
27819 {
27820 imgPath = StatusEffectQueue_t::StatusEffectDefinitions_t::getEffectImgPath(definition, variation);
27821 }
27822 if ( imgPath != "" )
27823 {
27824 srcSurf = const_cast<SDL_Surface*>(Image::get(imgPath.c_str())->getSurf());
27825
27826 bool blinking = false;
27827 if ( (enemy_statusEffectsLowDuration1 & (1 << i)) != 0 )
27828 {
27829 blinking = true;
27830 }
27831 statusEffectIcons.push_back(std::make_pair(srcSurf, blinking));
27832 }
27833 }
27834 }
27835 }
27836 }
27837 }
27838 if ( enemy_statusEffects2 != 0 )
27839 {
27840 for ( int i = 0; i < 32; ++i )
27841 {
27842 if ( (enemy_statusEffects2 & (1 << i)) != 0 )
27843 {
27844 int effectID = i + 32;
27845 if ( StatusEffectQueue_t::StatusEffectDefinitions_t::effectDefinitionExists(effectID) )
27846 {
27847 int variation = -1;
27848 SDL_Surface* srcSurf = nullptr;
27849 if ( i == EFF_SHAPESHIFT )
27850 {
27851 if ( entity && entity->behavior == &actPlayer )
27852 {
27853 switch ( entity->effectShapeshift )
27854 {
27855 case RAT:
27856 variation = 0;
27857 break;
27858 case SPIDER:
27859 variation = 1;
27860 break;
27861 case TROLL:
27862 variation = 2;
27863 break;
27864 case CREATURE_IMP:
27865 variation = 3;
27866 break;
27867 default:
27868 break;
27869 }
27870 }
27871 }
27872 auto& definition = StatusEffectQueue_t::StatusEffectDefinitions_t::getEffect(effectID);
27873 if ( !definition.neverDisplay )
27874 {
27875 std::string imgPath;
27876 if ( i == EFF_SHAPESHIFT && variation == -1 )
27877 {
27878 imgPath = "";
27879 }
27880 else
27881 {
27882 imgPath = StatusEffectQueue_t::StatusEffectDefinitions_t::getEffectImgPath(definition, variation);
27883 }
27884 if ( imgPath != "" )
27885 {
27886 srcSurf = const_cast<SDL_Surface*>(Image::get(imgPath.c_str())->getSurf());
27887
27888 bool blinking = false;
27889 if ( (enemy_statusEffectsLowDuration2 & (1 << i)) != 0 )
27890 {
27891 blinking = true;
27892 }
27893 statusEffectIcons.push_back(std::make_pair(srcSurf, blinking));
27894 }
27895 }
27896 }
27897 }
27898 }
27899 }
27900
27901 //const int numIcons = statusEffectIcons.size();
27902 //const int iconTotalWidth = iconWidth + 2;
27903 //if ( numIcons % 2 == 1 ) // odd numbered
27904 //{
27905 // currentX -= ((iconTotalWidth) * (numIcons / 2)) + (iconTotalWidth / 2);
27906 //}
27907 //else
27908 //{
27909 // currentX -= ((iconTotalWidth) * (numIcons / 2));
27910 //}
27911
27912 if ( statusEffectIcons.empty() )
27913 {
27914 return nullptr;
27915 }
27916
27917 SDL_Surface* sprite = SDL_CreateRGBSurface(0, (iconWidth + 2) * statusEffectIcons.size(), iconHeight + 2, 32,
27918 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
27919
27920 for ( auto& icon : statusEffectIcons )
27921 {
27922 anyStatusEffect = true;
27923 int tickModifier = ticks % 25;
27924 real_t alpha = 255;
27925 /*if ( !EnemyHPDamageBarHandler::bEnemyBarSimpleBlit )
27926 {
27927 const real_t frameOpacity = frame->getOpacity() / 100.0;
27928 alpha *= frameOpacity;
27929 }*/
27930 if ( tickModifier >= 12 && icon.second )
27931 {
27932 alpha = 0;
27933 }
27934 SDL_SetSurfaceAlphaMod(icon.first, alpha);
27935 //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
27936 SDL_Rect pos{ currentX, 0, iconWidth, iconHeight };
27937 SDL_BlitScaledSDL_UpperBlitScaled(icon.first, nullptr, sprite, &pos);
27938 currentX += (iconWidth + 2);
27939 }
27940
27941 if ( !hashSurf && EnemyHPDamageBarHandler::bEnemyBarSimpleBlit )
27942 {
27943 enemyBarEffectMapInsert(
27944 enemy_statusEffects1,
27945 enemy_statusEffects2,
27946 enemy_statusEffectsLowDuration1,
27947 enemy_statusEffectsLowDuration2,
27948 (ticks % 25) >= 12,
27949 sprite);
27950 }
27951 return sprite;
27952}
27953
27954EnemyHPDamageBarHandler::EnemyHPDetails::~EnemyHPDetails()
27955{
27956 if ( worldTexture )
27957 {
27958 delete worldTexture;
27959 worldTexture = nullptr;
27960 }
27961 if ( !bEnemyBarSimpleBlit )
27962 {
27963 if ( worldSurfaceSpriteStatusEffects ) {
27964 SDL_FreeSurface(worldSurfaceSpriteStatusEffects);
27965 worldSurfaceSpriteStatusEffects = nullptr;
27966 }
27967 if ( worldSurfaceSprite ) {
27968 SDL_FreeSurface(worldSurfaceSprite);
27969 worldSurfaceSprite = nullptr;
27970 }
27971 }
27972}
27973
27974void Player::HUD_t::updateEnemyBar2(Frame* whichFrame, void* enemyHPDetails)
27975{
27976 if ( !whichFrame )
27977 {
27978 return;
27979 }
27980
27981 if ( !enemyHPDetails )
27982 {
27983 return;
27984 }
27985
27986 if ( !player.isLocalPlayer() )
27987 {
27988 return;
27989 }
27990
27991 EnemyHPDamageBarHandler::EnemyHPDetails* enemyDetails = static_cast<EnemyHPDamageBarHandler::EnemyHPDetails*>(enemyHPDetails);
27992
27993 Entity* entity = uidToEntity(enemyDetails->enemy_uid);
27994 if ( entity )
27995 {
27996 //enemyDetails->updateWorldCoordinates(); --moved to main loop before drawEntities3D
27997 if ( entity->behavior == &actMonster && entity->getMonsterTypeFromSprite() == MIMIC )
27998 {
27999 if ( entity->isInertMimic() )
28000 {
28001 enemyDetails->barType = EnemyHPDamageBarHandler::BAR_TYPE_FURNITURE;
28002 }
28003 else
28004 {
28005 enemyDetails->barType = EnemyHPDamageBarHandler::BAR_TYPE_CREATURE;
28006 }
28007 }
28008 }
28009 else
28010 {
28011 enemyDetails->enemy_hp = 0;
28012 }
28013
28014 bool bIsMostRecentHPBar = enemyHPDamageBarHandler[player.playernum].getMostRecentHPBar() == enemyDetails;
28015 //if ( bIsMostRecentHPBar && !enemyDetails->hasDistanceCheck )
28016 //{
28017 // //enemyDetails->hasDistanceCheck = true;
28018 // auto& camera = cameras[player.playernum];
28019 // double playerdist = sqrt(pow(camera.x * 16.0 - enemyDetails->worldX, 2) + pow(camera.y * 16.0 - enemyDetails->worldY, 2));
28020 // if ( playerdist >= 3 * 16.0 )
28021 // {
28022 // //enemyDetails->displayOnHUD = true;
28023 // }
28024 //}
28025
28026 SDL_Rect pos = whichFrame->getSize();
28027 bool doFadeout = false;
28028 bool doAnimation = true;
28029 if ( enemyDetails->displayOnHUD && whichFrame == enemyBarFrameHUD )
28030 {
28031 doAnimation = false;
28032 }
28033
28034 if ( gamePaused && multiplayer == SINGLE0 )
28035 {
28036 enemyDetails->enemy_timer = ticks;
28037 return;
28038 }
28039
28040 if ( doAnimation && !enemyDetails->displayOnHUD && !enemyDetails->expired && enemyDetails->animator.setpoint <= 0 )
28041 {
28042 if ( ticks - enemyDetails->enemy_timer >= EnemyHPDamageBarHandler::shortDistanceHPBarFadeTicks )
28043 {
28044 if ( splitscreen )
28045 {
28046 // if anyone in splitscreen is near the bar, expire it instead of just the owner
28047 for ( int i = 0; i < MAXPLAYERS4; ++i )
28048 {
28049 if ( i == player.playernum || players[i]->isLocalPlayerAlive() )
28050 {
28051 auto& camera = cameras[i];
28052 double playerdist = sqrt(pow(camera.x * 16.0 - enemyDetails->worldX, 2) + pow(camera.y * 16.0 - enemyDetails->worldY, 2));
28053 if ( playerdist <= EnemyHPDamageBarHandler::shortDistanceHPBarFadeDistance * 16.0 )
28054 {
28055 enemyDetails->expired = true;
28056 break;
28057 }
28058 }
28059 }
28060 }
28061 else
28062 {
28063 auto& camera = cameras[player.playernum];
28064 double playerdist = sqrt(pow(camera.x * 16.0 - enemyDetails->worldX, 2) + pow(camera.y * 16.0 - enemyDetails->worldY, 2));
28065 if ( playerdist <= EnemyHPDamageBarHandler::shortDistanceHPBarFadeDistance * 16.0 )
28066 {
28067 enemyDetails->expired = true;
28068 }
28069 }
28070 }
28071 }
28072
28073 if ( enemyDetails->expired == true )
28074 {
28075 doFadeout = true;
28076 }
28077 else
28078 {
28079 enemyDetails->animator.fadeOut = 100.0;
28080 }
28081
28082 if ( doFadeout )
28083 {
28084 if ( doAnimation )
28085 {
28086 enemyDetails->animator.fadeOut -= 10.0 * getFPSScale(60.0);
28087 if ( enemyDetails->animator.fadeOut < 0.0 )
28088 {
28089 enemyDetails->animator.fadeOut = 0.0;
28090 enemyDetails->animator.fadeIn = 0.0;
28091 }
28092 }
28093 whichFrame->setOpacity(enemyDetails->animator.fadeOut);
28094 }
28095 else
28096 {
28097 enemyDetails->animator.fadeIn = 100.0;
28098 whichFrame->setOpacity(enemyDetails->animator.fadeIn);
28099 }
28100
28101 auto baseBg = whichFrame->findImage("base img");
28102 auto baseEndCap = whichFrame->findImage("base img endcap");
28103
28104 auto foregroundFrame = whichFrame->findFrame("bar progress frame");
28105 auto hpProgress = foregroundFrame->findImage("progress img");
28106 auto hpProgressEndcap = foregroundFrame->findImage("progress img endcap");
28107
28108 auto dmgFrame = whichFrame->findFrame("bar dmg frame");
28109 auto dmgProgress = dmgFrame->findImage("dmg img");
28110 auto dmgEndCap = dmgFrame->findImage("dmg img endcap");
28111 //auto bubblesImg = dmgFrame->findImage("img bubbles");
28112
28113 real_t progressWidth = ENEMYBAR_BAR_WIDTH - hpProgressEndcap->pos.w;
28114 int backgroundWidth = ENEMYBAR_BAR_WIDTH - baseEndCap->pos.w;
28115
28116 auto nameTxt = whichFrame->findField("enemy name txt");
28117
28118 // handle bar size changing
28119 {
28120 std::vector<std::pair<real_t, int>> widthHealthBreakpoints;
28121 if ( enemyDetails->barType == EnemyHPDamageBarHandler::BAR_TYPE_CREATURE )
28122 {
28123 widthHealthBreakpoints = EnemyHPDamageBarHandler::widthHealthBreakpointsMonsters;
28124 }
28125 else
28126 {
28127 widthHealthBreakpoints = EnemyHPDamageBarHandler::widthHealthBreakpointsFurniture;
28128 }
28129 if ( widthHealthBreakpoints.empty() ) // width %, then HP value
28130 {
28131 // build some defaults
28132 widthHealthBreakpoints.push_back(std::make_pair(0.5, 10));
28133 widthHealthBreakpoints.push_back(std::make_pair(0.60, 20));
28134 widthHealthBreakpoints.push_back(std::make_pair(0.70, 50));
28135 widthHealthBreakpoints.push_back(std::make_pair(0.80, 100));
28136 widthHealthBreakpoints.push_back(std::make_pair(0.90, 250));
28137 widthHealthBreakpoints.push_back(std::make_pair(1.00, 1000));
28138 }
28139
28140 enemyDetails->animator.widthMultiplier = 1.0;
28141 auto prevIt = widthHealthBreakpoints.end();
28142 bool foundBreakpoint = false;
28143 for ( auto it = widthHealthBreakpoints.begin(); it != widthHealthBreakpoints.end(); ++it )
28144 {
28145 int healthThreshold = (*it).second;
28146 real_t widthEntry = (*it).first;
28147 if ( (int)enemyDetails->animator.maxValue <= healthThreshold )
28148 {
28149 real_t width = 0.0;
28150 if ( prevIt != widthHealthBreakpoints.end() )
28151 {
28152 width = (*prevIt).first;
28153 // get linear scaled value between the breakponts
28154 width += (widthEntry - (*prevIt).first) * ((int)enemyDetails->animator.maxValue - (*prevIt).second) / ((*it).second - (*prevIt).second);
28155 }
28156 else
28157 {
28158 // set as minimum width
28159 width = widthEntry;
28160 }
28161 enemyDetails->animator.widthMultiplier = width;
28162 foundBreakpoint = true;
28163 break;
28164 }
28165 prevIt = it;
28166 }
28167 if ( !foundBreakpoint )
28168 {
28169 enemyDetails->animator.widthMultiplier = widthHealthBreakpoints[widthHealthBreakpoints.size() - 1].first;
28170 }
28171
28172 real_t multiplier = enemyDetails->animator.widthMultiplier;
28173
28174 int diff = static_cast<int>(std::max(0.0, progressWidth - progressWidth * multiplier)); // how many pixels the progress bar shrinks
28175 progressWidth *= multiplier; // scale the progress bars
28176 baseBg->pos.w = backgroundWidth - diff; // move the background bar by x pixels
28177 baseEndCap->pos.x = baseBg->pos.x + baseBg->pos.w; // move the background endcap by the new width
28178
28179 hpProgress->pos.w = progressWidth;
28180 hpProgressEndcap->pos.x = hpProgress->pos.x + hpProgress->pos.w;
28181
28182 pos.x = (hudFrame->getSize().w / 2) - ENEMYBAR_FRAME_WIDTH / 2 - 6;
28183 int widthChange = ((ENEMYBAR_BAR_WIDTH - hpProgressEndcap->pos.w) - progressWidth);
28184 pos.x += widthChange / 2;
28185 SDL_Rect textPos = nameTxt->getSize();
28186 textPos.w = ENEMYBAR_FRAME_WIDTH - widthChange;
28187 textPos.y = baseBg->pos.y;
28188 textPos.h = baseBg->pos.h;
28189 nameTxt->setSize(textPos);
28190 }
28191
28192 int startY = hudFrame->getSize().h - ENEMYBAR_FRAME_START_Y - 100;
28193 if ( doFadeout )
28194 {
28195 pos.y = startY + (15 * (100.0 - enemyDetails->animator.fadeOut) / 100.0); // slide out downwards
28196 }
28197 else
28198 {
28199 pos.y = startY;
28200 }
28201 whichFrame->setSize(pos);
28202
28203 enemyDetails->animator.previousSetpoint = enemyDetails->animator.setpoint;
28204 real_t& hpForegroundValue = enemyDetails->animator.foregroundValue;
28205 real_t& hpFadedValue = enemyDetails->animator.backgroundValue;
28206
28207 if ( doAnimation )
28208 {
28209 enemyDetails->animator.setpoint = enemyDetails->enemy_hp;
28210 if ( enemyDetails->animator.setpoint < enemyDetails->animator.previousSetpoint ) // insta-change as losing health
28211 {
28212 hpForegroundValue = enemyDetails->animator.setpoint;
28213 enemyDetails->animator.animateTicks = ticks;
28214 }
28215
28216 if ( enemyDetails->animator.maxValue > enemyDetails->enemy_maxhp )
28217 {
28218 hpFadedValue = enemyDetails->animator.setpoint; // resetting game etc, stop fade animation sticking out of frame
28219 }
28220 enemyDetails->animator.maxValue = enemyDetails->enemy_maxhp;
28221
28222
28223 if ( hpForegroundValue < enemyDetails->animator.setpoint ) // gaining HP, animate
28224 {
28225 real_t setpointDiff = std::max(0.01, enemyDetails->animator.setpoint - hpForegroundValue);
28226 real_t fpsScale = getFPSScale(144.0);
28227 hpForegroundValue += fpsScale * (setpointDiff / 20.0); // reach it in 20 intervals, scaled to FPS
28228 hpForegroundValue = std::min(static_cast<real_t>(enemyDetails->animator.setpoint), hpForegroundValue);
28229
28230 if ( abs(enemyDetails->animator.setpoint) - abs(hpForegroundValue) <= .05 )
28231 {
28232 hpForegroundValue = enemyDetails->animator.setpoint;
28233 }
28234 }
28235 else if ( hpForegroundValue > enemyDetails->animator.setpoint ) // losing HP, snap to value
28236 {
28237 hpForegroundValue = enemyDetails->animator.setpoint;
28238 }
28239
28240 if ( hpFadedValue < enemyDetails->animator.setpoint )
28241 {
28242 hpFadedValue = hpForegroundValue;
28243 enemyDetails->animator.animateTicks = ticks;
28244 }
28245 else if ( hpFadedValue > enemyDetails->animator.setpoint )
28246 {
28247 if ( ticks - enemyDetails->animator.animateTicks > 30 || enemyDetails->animator.setpoint <= 0 ) // fall after x ticks
28248 {
28249 real_t setpointDiff = std::max(0.01, hpFadedValue - enemyDetails->animator.setpoint);
28250 real_t fpsScale = getFPSScale(144.0);
28251 real_t intervals = 10.0;
28252 if ( enemyDetails->animator.setpoint <= 0 )
28253 {
28254 intervals = 30.0; // dramatic
28255 }
28256 hpFadedValue -= fpsScale * (setpointDiff / intervals); // reach it in X intervals, scaled to FPS
28257 hpFadedValue = std::max(static_cast<real_t>(enemyDetails->animator.setpoint), hpFadedValue);
28258 }
28259 }
28260 else
28261 {
28262 enemyDetails->animator.animateTicks = ticks;
28263 }
28264 }
28265
28266 auto skullFrame = whichFrame->findFrame("skull frame");
28267 std::vector<std::pair<Frame::image_t*, int>> allSkulls;
28268 allSkulls.push_back(std::make_pair(skullFrame->findImage("skull 0 img"), 0));
28269 allSkulls.push_back(std::make_pair(skullFrame->findImage("skull 25 img"), 25));
28270 allSkulls.push_back(std::make_pair(skullFrame->findImage("skull 50 img"), 50));
28271 allSkulls.push_back(std::make_pair(skullFrame->findImage("skull 100 img"), 75));
28272 real_t healthPercentage = 100 * enemyDetails->animator.setpoint / std::max(1.0, enemyDetails->animator.maxValue);
28273 int skullIndex = 0;
28274 for ( auto& skull : allSkulls )
28275 {
28276 if ( !skull.first ) { continue; }
28277
28278 Uint8 r, g, b, a;
28279 getColor(skull.first->color, &r, &g, &b, &a);
28280 real_t& skullOpacity = enemyDetails->animator.skullOpacities[skullIndex];
28281
28282 if ( doAnimation )
28283 {
28284 if ( (int)healthPercentage < skull.second )
28285 {
28286 real_t opacityChange = 4 * getFPSScale(60.0); // change independent of fps
28287 skullOpacity = std::max(0, (int)(skullOpacity - opacityChange));
28288 }
28289 else if ( (int)healthPercentage >= skull.second )
28290 {
28291 real_t opacityChange = 255;// *getFPSScale(144.0); // change independent of fps
28292 skullOpacity = std::min(255, (int)(skullOpacity + opacityChange));
28293 }
28294 }
28295 a = skullOpacity;
28296 //a *= enemyDetails->animator.fadeOut / 100.0;
28297 skull.first->color = makeColor( r, g, b, a);
28298 ++skullIndex;
28299 }
28300
28301 nameTxt->setText(enemyDetails->enemy_name.c_str());
28302
28303 real_t foregroundPercent = hpForegroundValue / enemyDetails->animator.maxValue;
28304 hpProgress->pos.w = std::max(1, static_cast<int>((progressWidth)* foregroundPercent));
28305 hpProgressEndcap->pos.x = hpProgress->pos.x + hpProgress->pos.w;
28306
28307 real_t fadePercent = hpFadedValue / enemyDetails->animator.maxValue;
28308 dmgProgress->pos.w = std::max(1, static_cast<int>((progressWidth)* fadePercent));
28309 dmgEndCap->pos.x = dmgProgress->pos.x + dmgProgress->pos.w;
28310 if ( dmgProgress->pos.w == 1 && enemyDetails->animator.setpoint <= 0 )
28311 {
28312 dmgProgress->disabled = true;
28313 //real_t& opacity = enemyDetails->animator.damageFrameOpacity;
28314 //if ( doAnimation )
28315 //{
28316 // real_t opacityChange = .5 * getFPSScale(144.0); // change by .05% independent of fps
28317 // opacity = std::max(0.0, opacity - opacityChange);
28318 //}
28319 //dmgFrame->setOpacity(opacity);
28320 //dmgFrame->setOpacity(dmgFrame->getOpacity() * enemyDetails->animator.fadeOut / 100.0);
28321 //// make this element fade out to the left
28322 //dmgEndCap->pos.x = 0 - (dmgEndCap->pos.w * (1.0 - opacity / 100.0));
28323 }
28324 else
28325 {
28326 dmgProgress->disabled = false;
28327 enemyDetails->animator.damageFrameOpacity = enemyDetails->animator.fadeOut;
28328 dmgFrame->setOpacity(enemyDetails->animator.fadeOut);
28329 }
28330
28331 if ( enemyDetails->animator.setpoint <= 0 )
28332 {
28333 // hide all the progress elements when dead, as endcap/base don't shrink
28334 // hpProgress width 0px defaults to original size, so hide that too
28335 hpProgress->disabled = true;
28336 hpProgressEndcap->disabled = true;
28337 }
28338 else
28339 {
28340 hpProgress->disabled = false;
28341 hpProgressEndcap->disabled = false;
28342 }
28343
28344 // damage number on HUD
28345 if ( enemyDetails->displayOnHUD && whichFrame == enemyBarFrameHUD )
28346 {
28347 auto dmgText = hudFrame->findField("enemy dmg txt");
28348 Sint32 damageNumber = enemyDetails->animator.damageTaken;
28349 enemyDetails->animator.damageTaken = -1;
28350 if ( damageNumber >= 0 )
28351 {
28352 char buf[128] = "";
28353 snprintf(buf, sizeof(buf), "%d", damageNumber);
28354 dmgText->setText(buf);
28355 dmgText->setDisabled(false);
28356 SDL_Rect txtPos = dmgText->getSize();
28357 SDL_Rect barPos = foregroundFrame->getAbsoluteSize();
28358 //txtPos.x = barPos.x + baseBg->pos.x/* + baseBg->pos.w*/;
28359 //txtPos.y = barPos.y - 30;
28360 auto text = Text::get(dmgText->getText(), dmgText->getFont(),
28361 makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255));
28362 txtPos.x = 30 + player.camera_virtualWidth() / 2;
28363 txtPos.y = -50 + player.camera_virtualHeight() / 2;
28364 txtPos.w = text->getWidth();
28365 txtPos.h = text->getHeight();
28366 dmgText->setSize(txtPos);
28367 hudDamageTextVelocityX = 1.0;
28368 hudDamageTextVelocityY = 3.0;
28369 Uint8 r, g, b, a;
28370 getColor(dmgText->getColor(), &r, &g, &b, &a);
28371 dmgText->setColor(makeColor(r, g, b, 255));
28372 }
28373
28374 if ( !dmgText->isDisabled() )
28375 {
28376 SDL_Rect txtPos = dmgText->getSize();
28377 txtPos.x += hudDamageTextVelocityX;
28378 txtPos.y -= hudDamageTextVelocityY;
28379 hudDamageTextVelocityX += .05;
28380 hudDamageTextVelocityY -= .3;
28381 dmgText->setSize(txtPos);
28382 if ( hudDamageTextVelocityY < -2.0 )
28383 {
28384 Uint8 r, g, b, a;
28385 getColor(dmgText->getColor(), &r, &g, &b, &a);
28386 a = (std::max(0, (int)a - 16));
28387 dmgText->setColor(makeColor(r, g, b, a));
28388 if ( a == 0 )
28389 {
28390 dmgText->setDisabled(true);
28391 }
28392 }
28393 }
28394 }
28395
28396
28397 bool oldBlitType = EnemyHPDamageBarHandler::bEnemyBarSimpleBlit;
28398 EnemyHPDamageBarHandler::bEnemyBarSimpleBlit = *cvar_enemybar_simple_blit;
28399
28400 //auto blit = std::chrono::high_resolution_clock::now();
28401 if ( !EnemyHPDamageBarHandler::bEnemyBarSimpleBlit )
28402 {
28403 if ( !enemyBarMap.m.empty() )
28404 {
28405 enemyBarEffectMap.m.clear();
28406 enemyBarMap.m.clear();
28407 enemyDetails->worldSurfaceSprite = nullptr;
28408 enemyDetails->worldSurfaceSpriteStatusEffects = nullptr;
28409 }
28410
28411 if ( enemyDetails->worldTexture )
28412 {
28413 delete enemyDetails->worldTexture;
28414 enemyDetails->worldTexture = nullptr;
28415 }
28416 if ( enemyDetails->worldSurfaceSprite )
28417 {
28418 SDL_FreeSurface(enemyDetails->worldSurfaceSprite);
28419 enemyDetails->worldSurfaceSprite = nullptr;
28420 }
28421 if ( enemyDetails->worldSurfaceSpriteStatusEffects )
28422 {
28423 SDL_FreeSurface(enemyDetails->worldSurfaceSpriteStatusEffects);
28424 enemyDetails->worldSurfaceSpriteStatusEffects = nullptr;
28425 }
28426
28427 enemyDetails->worldSurfaceSpriteStatusEffects = enemyDetails->blitEnemyBarStatusEffects(player.playernum);
28428 //auto blit2 = std::chrono::high_resolution_clock::now();
28429 enemyDetails->worldSurfaceSprite = enemyDetails->blitEnemyBar(player.playernum, enemyDetails->worldSurfaceSpriteStatusEffects);
28430 //auto blit3 = std::chrono::high_resolution_clock::now();
28431 enemyDetails->worldTexture = new TempTexture();
28432 enemyDetails->worldTexture->load(enemyDetails->worldSurfaceSprite, false, true);
28433 //auto blit4 = std::chrono::high_resolution_clock::now();
28434 //float ms1 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(blit2 - blit).count();
28435 //float ms2 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(blit3 - blit2).count();
28436 //float ms3 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(blit4 - blit3).count();
28437 //float msTotal = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(blit4 - blit).count();
28438 //printTextFormatted(font16x16_bmp, 8, 8 + 4 * 16, "Total: %.4f\n ms1: %.4f\n ms2: %.4f\nms3: %.4f", msTotal, ms1, ms2, ms3);
28439 }
28440 else
28441 {
28442 enemyDetails->worldSurfaceSpriteStatusEffects = enemyDetails->blitEnemyBarStatusEffects(player.playernum);
28443 //auto blit2 = std::chrono::high_resolution_clock::now();
28444 SDL_Surface* oldWorldSprite = enemyDetails->worldSurfaceSprite;
28445 enemyDetails->worldSurfaceSprite = enemyDetails->blitEnemyBar(player.playernum, enemyDetails->worldSurfaceSpriteStatusEffects);
28446 //auto blit3 = std::chrono::high_resolution_clock::now();
28447 if ( oldWorldSprite != enemyDetails->worldSurfaceSprite )
28448 {
28449 if ( enemyDetails->worldTexture )
28450 {
28451 delete enemyDetails->worldTexture;
28452 enemyDetails->worldTexture = nullptr;
28453 }
28454
28455 if ( enemyDetails->worldSurfaceSprite )
28456 {
28457 enemyDetails->worldTexture = new TempTexture();
28458 enemyDetails->worldTexture->load(enemyDetails->worldSurfaceSprite, false, true);
28459 }
28460 }
28461 //auto blit4 = std::chrono::high_resolution_clock::now();
28462 //float ms1 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(blit2 - blit).count();
28463 //float ms2 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(blit3 - blit2).count();
28464 //float ms3 = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(blit4 - blit3).count();
28465 //float msTotal = 1000 * std::chrono::duration_cast<std::chrono::duration<double>>(blit4 - blit).count();
28466 //printTextFormatted(font16x16_bmp, 8, 8 + 4 * 16, "Total: %.4f\n ms1: %.4f\n ms2: %.4f\nms3: %.4f", msTotal, ms1, ms2, ms3);
28467 }
28468
28469
28470
28471 whichFrame->setDisabled(true);
28472 if ( !enemyDetails->displayOnHUD )
28473 {
28474 //printTextFormatted(font16x16_bmp, 8, 8 + 4 * 16, "Any vertex visible: %d", anyVertexVisible);
28475 }
28476 else
28477 {
28478 if ( whichFrame == enemyBarFrameHUD )
28479 {
28480 whichFrame->setDisabled(false);
28481 }
28482 else
28483 {
28484 updateEnemyBar2(enemyBarFrameHUD, enemyHPDetails);
28485 }
28486 }
28487}
28488
28489static ConsoleCommand ccmd_enemybar_dump_cache("/enemybar_dump_cache", "Dumps cached enemy bars",
28490 [](int argc, const char** argv) {
28491 EnemyHPDamageBarHandler::dumpCache();
28492});
28493
28494void EnemyHPDamageBarHandler::dumpCache()
28495{
28496 enemyBarEffectMap.m.clear();
28497 enemyBarMap.m.clear();
28498 for ( int i = 0; i < MAXPLAYERS4; ++i )
28499 {
28500 enemyHPDamageBarHandler[i].HPBars.clear();
28501 }
28502}
28503
28504void Player::HUD_t::updateEnemyBar(Frame* whichFrame)
28505{
28506 if ( !whichFrame )
28507 {
28508 return;
28509 }
28510
28511 if ( !player.isLocalPlayer() )
28512 {
28513 return;
28514 }
28515
28516 SDL_Rect pos = whichFrame->getSize();
28517 bool doFadeout = false;
28518
28519 EnemyHPDamageBarHandler::EnemyHPDetails* enemyDetails = nullptr;
28520 Bar_t* enemyBar = nullptr;
28521 Sint32 damageNumber = -1;
28522 enemyDetails = enemyHPDamageBarHandler[player.playernum].getMostRecentHPBar();
28523 enemyBar = &this->enemyBar;
28524
28525 if ( enemyDetails )
28526 {
28527 enemyBar->animatePreviousSetpoint = enemyDetails->animator.previousSetpoint;
28528 enemyBar->animateValue = enemyDetails->animator.foregroundValue;
28529 enemyBar->animateValue2 = enemyDetails->animator.backgroundValue;
28530 enemyBar->animateSetpoint = enemyDetails->animator.setpoint;
28531 enemyBar->animateTicks = enemyDetails->animator.animateTicks;
28532 enemyBar->maxValue = enemyDetails->animator.maxValue;
28533 enemyBar->fadeOut = 100.0;
28534 damageNumber = enemyDetails->animator.damageTaken;
28535 enemyDetails->animator.damageTaken = -1;
28536
28537 if ( Entity* entity = uidToEntity(enemyDetails->enemy_uid) )
28538 {
28539 enemyDetails->worldX = entity->x;
28540 enemyDetails->worldY = entity->y;
28541 enemyDetails->worldZ = entity->z;
28542 }
28543 }
28544 if ( !enemyDetails || enemyDetails->expired == true )
28545 {
28546 doFadeout = true;
28547 }
28548
28549 if ( doFadeout )
28550 {
28551 enemyBar->fadeOut -= 10.0 * getFPSScale(60.0);
28552 if ( enemyBar->fadeOut < 0.0 ) { enemyBar->fadeOut = 0.0; enemyBar->fadeIn = 0.0; }
28553 whichFrame->setOpacity(enemyBar->fadeOut);
28554 }
28555 else
28556 {
28557 enemyBar->fadeIn = 100.0;
28558 whichFrame->setOpacity(enemyBar->fadeIn);
28559 }
28560
28561 auto baseBg = whichFrame->findImage("base img");
28562 auto baseEndCap = whichFrame->findImage("base img endcap");
28563
28564 auto foregroundFrame = whichFrame->findFrame("bar progress frame");
28565 auto hpProgress = foregroundFrame->findImage("progress img");
28566 auto hpProgressEndcap = foregroundFrame->findImage("progress img endcap");
28567
28568 auto dmgFrame = whichFrame->findFrame("bar dmg frame");
28569 auto dmgProgress = dmgFrame->findImage("dmg img");
28570 auto dmgEndCap = dmgFrame->findImage("dmg img endcap");
28571 //auto bubblesImg = dmgFrame->findImage("img bubbles");
28572
28573 real_t progressWidth = ENEMYBAR_BAR_WIDTH - hpProgressEndcap->pos.w;
28574 int backgroundWidth = ENEMYBAR_BAR_WIDTH - baseEndCap->pos.w;
28575
28576 auto nameTxt = whichFrame->findField("enemy name txt");
28577
28578 // handle bar size changing
28579 {
28580 std::vector<std::pair<real_t, int>>widthHealthBreakpoints; // width %, then HP value
28581 widthHealthBreakpoints.push_back(std::make_pair(0.5, 10));
28582 widthHealthBreakpoints.push_back(std::make_pair(0.60, 20));
28583 widthHealthBreakpoints.push_back(std::make_pair(0.70, 50));
28584 widthHealthBreakpoints.push_back(std::make_pair(0.80, 100));
28585 widthHealthBreakpoints.push_back(std::make_pair(0.90, 250));
28586 widthHealthBreakpoints.push_back(std::make_pair(1.00, 1000));
28587
28588 enemyBar->widthMultiplier = 1.0;
28589 auto prevIt = widthHealthBreakpoints.end();
28590 bool foundBreakpoint = false;
28591 for ( auto it = widthHealthBreakpoints.begin(); it != widthHealthBreakpoints.end(); ++it )
28592 {
28593 int healthThreshold = (*it).second;
28594 real_t widthEntry = (*it).first;
28595 if ( (int)enemyBar->maxValue <= healthThreshold )
28596 {
28597 real_t width = 0.0;
28598 if ( prevIt != widthHealthBreakpoints.end() )
28599 {
28600 width = (*prevIt).first;
28601 // get linear scaled value between the breakponts
28602 width += (widthEntry - (*prevIt).first) * ((int)enemyBar->maxValue - (*prevIt).second) / ((*it).second - (*prevIt).second);
28603 }
28604 else
28605 {
28606 // set as minimum width
28607 width = widthEntry; /**((int)enemyBar->maxValue) / ((*it).second);*/
28608 }
28609 enemyBar->widthMultiplier = width;
28610 foundBreakpoint = true;
28611 break;
28612 }
28613 prevIt = it;
28614 }
28615 if ( !foundBreakpoint )
28616 {
28617 enemyBar->widthMultiplier = widthHealthBreakpoints[widthHealthBreakpoints.size() - 1].first;
28618 }
28619
28620 real_t multiplier = enemyBar->widthMultiplier;
28621
28622 int diff = static_cast<int>(std::max(0.0, progressWidth - progressWidth * multiplier)); // how many pixels the progress bar shrinks
28623 progressWidth *= multiplier; // scale the progress bars
28624 baseBg->pos.w = backgroundWidth - diff; // move the background bar by x pixels
28625 baseEndCap->pos.x = baseBg->pos.x + baseBg->pos.w; // move the background endcap by the new width
28626
28627 hpProgress->pos.w = progressWidth;
28628 hpProgressEndcap->pos.x = hpProgress->pos.x + hpProgress->pos.w;
28629
28630 pos.x = (hudFrame->getSize().w / 2) - ENEMYBAR_FRAME_WIDTH / 2 - 6;
28631 int widthChange = ((ENEMYBAR_BAR_WIDTH - hpProgressEndcap->pos.w) - progressWidth);
28632 pos.x += widthChange / 2;
28633 SDL_Rect textPos = nameTxt->getSize();
28634 textPos.w = ENEMYBAR_FRAME_WIDTH - widthChange;
28635 textPos.y = baseBg->pos.y;
28636 textPos.h = baseBg->pos.h;
28637 nameTxt->setSize(textPos);
28638 }
28639
28640 int startY = hudFrame->getSize().h - ENEMYBAR_FRAME_START_Y - 100;
28641 if ( doFadeout )
28642 {
28643 pos.y = startY + (15 * (100.0 - enemyBar->fadeOut) / 100.0); // slide out downwards
28644 }
28645 else
28646 {
28647 pos.y = startY;
28648 }
28649 whichFrame->setSize(pos);
28650
28651 //messagePlayer(0, "%.2f | %.2f | %.2f | %d", enemyDetails->animateValue,
28652 // enemyDetails->animateValue2, enemyDetails->animatePreviousSetpoint, enemyDetails->animateSetpoint);
28653
28654 enemyBar->animatePreviousSetpoint = enemyBar->animateSetpoint;
28655 real_t& hpForegroundValue = enemyBar->animateValue;
28656 real_t& hpFadedValue = enemyBar->animateValue2;
28657
28658 if ( enemyDetails )
28659 {
28660 enemyBar->animateSetpoint = enemyDetails->enemy_hp;
28661 if ( enemyBar->animateSetpoint < enemyBar->animatePreviousSetpoint ) // insta-change as losing health
28662 {
28663 hpForegroundValue = enemyBar->animateSetpoint;
28664 enemyBar->animateTicks = ticks;
28665 }
28666
28667 if ( enemyBar->maxValue > enemyDetails->enemy_maxhp )
28668 {
28669 hpFadedValue = enemyBar->animateSetpoint; // resetting game etc, stop fade animation sticking out of frame
28670 }
28671 enemyBar->maxValue = enemyDetails->enemy_maxhp;
28672 }
28673
28674
28675 if ( hpForegroundValue < enemyBar->animateSetpoint ) // gaining HP, animate
28676 {
28677 real_t setpointDiff = std::max(0.01, enemyBar->animateSetpoint - hpForegroundValue);
28678 real_t fpsScale = getFPSScale(144.0);
28679 hpForegroundValue += fpsScale * (setpointDiff / 20.0); // reach it in 20 intervals, scaled to FPS
28680 hpForegroundValue = std::min(static_cast<real_t>(enemyBar->animateSetpoint), hpForegroundValue);
28681
28682 if ( abs(enemyBar->animateSetpoint) - abs(hpForegroundValue) <= .05 )
28683 {
28684 hpForegroundValue = enemyBar->animateSetpoint;
28685 }
28686 }
28687 else if ( hpForegroundValue > enemyBar->animateSetpoint ) // losing HP, snap to value
28688 {
28689 hpForegroundValue = enemyBar->animateSetpoint;
28690 }
28691
28692 if ( hpFadedValue < enemyBar->animateSetpoint )
28693 {
28694 hpFadedValue = hpForegroundValue;
28695 enemyBar->animateTicks = ticks;
28696 }
28697 else if ( hpFadedValue > enemyBar->animateSetpoint )
28698 {
28699 if ( ticks - enemyBar->animateTicks > 30 || enemyBar->animateSetpoint <= 0 ) // fall after x ticks
28700 {
28701 real_t setpointDiff = std::max(0.01, hpFadedValue - enemyBar->animateSetpoint);
28702 real_t fpsScale = getFPSScale(144.0);
28703 real_t intervals = 10.0;
28704 if ( enemyBar->animateSetpoint <= 0 )
28705 {
28706 intervals = 30.0; // dramatic
28707 }
28708 hpFadedValue -= fpsScale * (setpointDiff / intervals); // reach it in X intervals, scaled to FPS
28709 hpFadedValue = std::max(static_cast<real_t>(enemyBar->animateSetpoint), hpFadedValue);
28710 }
28711 }
28712 else
28713 {
28714 enemyBar->animateTicks = ticks;
28715 }
28716
28717 auto skullFrame = whichFrame->findFrame("skull frame");
28718 std::vector<std::pair<Frame::image_t*, int>> allSkulls;
28719 allSkulls.push_back(std::make_pair(skullFrame->findImage("skull 0 img"), 0));
28720 allSkulls.push_back(std::make_pair(skullFrame->findImage("skull 25 img"), 25));
28721 allSkulls.push_back(std::make_pair(skullFrame->findImage("skull 50 img"), 50));
28722 allSkulls.push_back(std::make_pair(skullFrame->findImage("skull 100 img"), 75));
28723 real_t healthPercentage = 100 * enemyBar->animateSetpoint / std::max(1.0, enemyBar->maxValue);
28724 for ( auto& skull : allSkulls )
28725 {
28726 if ( !skull.first ) { continue; }
28727
28728 Uint8 r, g, b, a;
28729 getColor(skull.first->color, &r, &g, &b, &a);
28730
28731 if ( (int)healthPercentage < skull.second )
28732 {
28733 real_t opacityChange = 4 * getFPSScale(60.0); // change independent of fps
28734 a = std::max(0, a - (int)opacityChange);
28735 }
28736 else if ( (int)healthPercentage >= skull.second )
28737 {
28738 real_t opacityChange = 255;// *getFPSScale(144.0); // change independent of fps
28739 a = std::min(255, a + (int)opacityChange);
28740 }
28741 a *= whichFrame->getOpacity() / 100.0;
28742 skull.first->color = makeColor( r, g, b, a);
28743 }
28744
28745 //char playerHPText[16];
28746 //snprintf(playerHPText, sizeof(playerHPText), "%d", stats[player.playernum]->HP);
28747 if ( enemyDetails )
28748 {
28749 nameTxt->setText(enemyDetails->enemy_name.c_str());
28750 }
28751
28752 //auto hpText = hpForegroundFrame->findField("hp text");
28753 //hpText->setText(playerHPText);
28754
28755 real_t foregroundPercent = hpForegroundValue / enemyBar->maxValue;
28756 hpProgress->pos.w = std::max(1, static_cast<int>((progressWidth) * foregroundPercent));
28757 hpProgressEndcap->pos.x = hpProgress->pos.x + hpProgress->pos.w;
28758
28759 real_t fadePercent = hpFadedValue / enemyBar->maxValue;
28760 dmgProgress->pos.w = std::max(1, static_cast<int>((progressWidth) * fadePercent));
28761 dmgEndCap->pos.x = dmgProgress->pos.x + dmgProgress->pos.w;
28762 if ( dmgProgress->pos.w == 1 && enemyBar->animateSetpoint <= 0 )
28763 {
28764 dmgProgress->disabled = true;
28765 real_t opacity = dmgFrame->getOpacity();
28766 real_t opacityChange = .5 * getFPSScale(144.0); // change by .05% independent of fps
28767 dmgFrame->setOpacity(std::max(0.0, opacity - opacityChange));
28768 dmgFrame->setOpacity(dmgFrame->getOpacity() * whichFrame->getOpacity() / 100.0);
28769 // make this element fade out to the left
28770 dmgEndCap->pos.x = 0 - (dmgEndCap->pos.w * (1.0 - opacity / 100.0));
28771 }
28772 else
28773 {
28774 dmgProgress->disabled = false;
28775 dmgFrame->setOpacity(whichFrame->getOpacity());
28776 }
28777
28778 //bubblesImg->pos.x = dmgEndCap->pos.x - bubblesImg->pos.w;
28779
28780 if ( enemyBar->animateSetpoint <= 0 )
28781 {
28782 // hide all the progress elements when dead, as endcap/base don't shrink
28783 // hpProgress width 0px defaults to original size, so hide that too
28784 hpProgress->disabled = true;
28785 hpProgressEndcap->disabled = true;
28786 }
28787 else
28788 {
28789 hpProgress->disabled = false;
28790 hpProgressEndcap->disabled = false;
28791 }
28792
28793 auto dmgText = hudFrame->findField("enemy dmg txt");
28794 // damage number on HUD
28795 {
28796 if ( damageNumber >= 0 )
28797 {
28798 char buf[128] = "";
28799 snprintf(buf, sizeof(buf), "%d", damageNumber);
28800 dmgText->setText(buf);
28801 dmgText->setDisabled(false);
28802 SDL_Rect txtPos = dmgText->getSize();
28803 SDL_Rect barPos = foregroundFrame->getAbsoluteSize();
28804 //txtPos.x = barPos.x + baseBg->pos.x/* + baseBg->pos.w*/;
28805 //txtPos.y = barPos.y - 30;
28806 auto text = Text::get(dmgText->getText(), dmgText->getFont(),
28807 makeColor(255, 255, 255, 255), makeColor(0, 0, 0, 255));
28808 txtPos.x = 30 + player.camera_virtualWidth() / 2;
28809 txtPos.y = -50 + player.camera_virtualHeight() / 2;
28810 txtPos.w = text->getWidth();
28811 txtPos.h = text->getHeight();
28812 dmgText->setSize(txtPos);
28813 hudDamageTextVelocityX = 1.0;
28814 hudDamageTextVelocityY = 3.0;
28815 Uint8 r, g, b, a;
28816 getColor(dmgText->getColor(), &r, &g, &b, &a);
28817 dmgText->setColor(makeColor(r, g, b, 255));
28818 }
28819
28820 if ( !dmgText->isDisabled() )
28821 {
28822 SDL_Rect txtPos = dmgText->getSize();
28823 txtPos.x += hudDamageTextVelocityX;
28824 txtPos.y -= hudDamageTextVelocityY;
28825 hudDamageTextVelocityX += .05;
28826 hudDamageTextVelocityY -= .3;
28827 dmgText->setSize(txtPos);
28828 if ( hudDamageTextVelocityY < -2.0 )
28829 {
28830 Uint8 r, g, b, a;
28831 getColor(dmgText->getColor(), &r, &g, &b, &a);
28832 a = (std::max(0, (int)a - 16));
28833 dmgText->setColor(makeColor(r, g, b, a));
28834 if ( a == 0 )
28835 {
28836 dmgText->setDisabled(true);
28837 }
28838 }
28839 }
28840 }
28841
28842 if ( enemyDetails )
28843 {
28844 enemyDetails->animator.previousSetpoint = enemyBar->animatePreviousSetpoint;
28845 enemyDetails->animator.foregroundValue = enemyBar->animateValue;
28846 enemyDetails->animator.backgroundValue = enemyBar->animateValue2;
28847 enemyDetails->animator.setpoint = enemyBar->animateSetpoint;
28848 enemyDetails->animator.animateTicks = enemyBar->animateTicks;
28849 enemyDetails->animator.maxValue = enemyBar->maxValue;
28850 enemyDetails->animator.fadeIn = enemyBar->fadeIn;
28851 enemyDetails->animator.fadeOut = enemyBar->fadeOut;
28852 if ( enemyDetails->worldTexture )
28853 {
28854 delete enemyDetails->worldTexture;
28855 enemyDetails->worldTexture = nullptr;
28856 }
28857 if ( enemyDetails->worldSurfaceSprite )
28858 {
28859 SDL_FreeSurface(enemyDetails->worldSurfaceSprite);
28860 enemyDetails->worldSurfaceSprite = nullptr;
28861 }
28862 enemyDetails->worldSurfaceSprite = enemyDetails->blitEnemyBar(player.playernum, enemyDetails->worldSurfaceSpriteStatusEffects);
28863 enemyDetails->worldTexture = new TempTexture();
28864 enemyDetails->worldTexture->load(enemyDetails->worldSurfaceSprite, false, true);
28865 }
28866
28867 whichFrame->setDisabled(true);
28868}
28869
28870const int HPMPdividerThresholdInterval = 20;
28871const int kHPMPWidthReduce2pWideClippedActionPrompts = 60;
28872//static ConsoleVariable<int> cvar_hpanimdebug("/hpmpanimdebug", 1);
28873void Player::HUD_t::updateHPBar()
28874{
28875 if ( !hpFrame )
28876 {
28877 return;
28878 }
28879
28880 bool bCompactWidth = false;
28881 bool bCompactHeight = player.bUseCompactGUIHeight();
28882 if ( player.bUseCompactGUIWidth() || (keystatus[SDLK_t] && enableDebugKeys) )
28883 {
28884 bCompactWidth = true;
28885 }
28886
28887 SDL_Rect pos = hpFrame->getSize();
28888 pos.w = HPMP_FRAME_WIDTH + (bCompactWidth ? hpmpbarCompactOffsetWidth : hpmpbarOffsetWidth);
28889 if ( !player.bUseCompactGUIWidth()
28890 && player.bUseCompactGUIHeight() && *MainMenu::clipped_splitscreen
28891 && bShortHPMPForActionBars )
28892 {
28893 // shorten if action prompts visible in clipped wide mode
28894 pos.w -= kHPMPWidthReduce2pWideClippedActionPrompts;
28895 }
28896 pos.x = HPMP_FRAME_START_X + ((bCompactWidth || bCompactHeight) ? hpmpbarCompactOffsetX : hpmpbarOffsetX);
28897 pos.y = hudFrame->getSize().h - HPMP_FRAME_START_Y + ((bCompactWidth || bCompactHeight) ? hpmpbarCompactOffsetY : hpmpbarOffsetY);
28898 pos.y -= player.hud.offsetHUDAboveHotbarHeight;
28899 hpFrame->setSize(pos);
28900
28901 auto hpForegroundFrame = hpFrame->findFrame("hp foreground frame");
28902 {
28903 auto _pos = hpForegroundFrame->getSize();
28904 _pos.w = pos.w;
28905 hpForegroundFrame->setSize(_pos);
28906 }
28907 auto hpBg = hpFrame->findImage("hp img base");
28908 auto hpEndcap = hpForegroundFrame->findImage("hp img endcap");
28909 auto hpProgressBot = hpForegroundFrame->findImage("hp img progress bot");
28910 auto hpProgress = hpForegroundFrame->findImage("hp img progress");
28911 auto hpProgressEndCap = hpForegroundFrame->findImage("hp img progress endcap");
28912 auto hpFadeFrame = hpFrame->findFrame("hp fade frame");
28913 {
28914 auto _pos = hpFadeFrame->getSize();
28915 _pos.w = pos.w;
28916 hpFadeFrame->setSize(_pos);
28917 }
28918 auto hpFadedBase = hpFadeFrame->findImage("hp img fade bot");
28919 auto hpFaded = hpFadeFrame->findImage("hp img fade");
28920 auto hpFadedEndCap = hpFadeFrame->findImage("hp img fade endcap");
28921
28922 real_t progressWidth = hpFrame->getSize().w - 74;
28923 int backgroundWidth = hpFrame->getSize().w - 54;
28924
28925 // handle bar size changing
28926 {
28927 real_t multiplier = 1.0;
28928 const Sint32 maxHPWidth = (bCompactWidth ? hpmpbarCompactMaxWidthAmount : hpmpbarMaxWidthAmount);
28929 if ( stats[player.playernum]->MAXHP < maxHPWidth )
28930 {
28931 // start at 30%, increase 2.5% every 5 HP past 20 MAXHP
28932 multiplier = (bCompactWidth ? hpmpbarCompactBasePercentSize : hpmpbarBasePercentSize) / 100.0;
28933 real_t widthIntervalPercent = (bCompactWidth ? hpmpbarCompactWidthIncreasePercentOnInterval : hpmpbarWidthIncreasePercentOnInterval) / 100.0;
28934 int intervalThreshold = (bCompactWidth ? hpmpbarCompactIntervalToIncreaseWidth : hpmpbarIntervalToIncreaseWidth);
28935 int baseIntervalStart = (bCompactWidth ? hpmpbarCompactIntervalStartValue : hpmpbarIntervalStartValue);
28936 multiplier += (widthIntervalPercent * ((std::max(0, stats[player.playernum]->MAXHP - baseIntervalStart) / intervalThreshold)));
28937 }
28938
28939 int diff = static_cast<int>(std::max(0.0, progressWidth - progressWidth * multiplier)); // how many pixels the progress bar shrinks
28940 progressWidth *= multiplier; // scale the progress bars
28941 hpBg->pos.w = backgroundWidth - diff; // move the background bar by x pixels as above
28942 hpEndcap->pos.x = hpFrame->getSize().w - hpEndcap->pos.w - diff; // move the background endcap by x pixels as above
28943 }
28944
28945 HPBar.animatePreviousSetpoint = HPBar.animateSetpoint;
28946 real_t& hpForegroundValue = HPBar.animateValue;
28947 real_t& hpFadedValue = HPBar.animateValue2;
28948
28949 HPBar.animateSetpoint = stats[player.playernum]->HP;
28950 if ( HPBar.animateSetpoint < HPBar.animatePreviousSetpoint ) // insta-change as losing health
28951 {
28952 hpForegroundValue = HPBar.animateSetpoint;
28953 HPBar.animateTicks = ticks;
28954
28955 // flash for taking damage
28956 HPBar.flashTicks = ticks;
28957 HPBar.flashAnimState = -1;
28958 HPBar.flashType = FLASH_ON_DAMAGE;
28959 }
28960
28961 if ( HPBar.maxValue > stats[player.playernum]->MAXHP )
28962 {
28963 hpFadedValue = HPBar.animateSetpoint; // resetting game etc, stop fade animation sticking out of frame
28964 }
28965
28966 HPBar.maxValue = stats[player.playernum]->MAXHP;
28967
28968 if ( hpForegroundValue < HPBar.animateSetpoint ) // gaining HP, animate
28969 {
28970 // flash for gaining HP, provided not already flashing
28971 /*if ( HPBar.flashAnimState == -1 || (HPBar.flashAnimState >= 0 && HPBar.flashType != FLASH_ON_DAMAGE) )
28972 {
28973 HPBar.flashTicks = ticks;
28974 HPBar.flashAnimState = -1;
28975 HPBar.flashType = FLASH_ON_RECOVERY;
28976 }*/
28977
28978 real_t setpointDiff = std::max(0.0, HPBar.animateSetpoint - hpForegroundValue);
28979 real_t fpsScale = getFPSScale(144.0);
28980 hpForegroundValue += fpsScale * (setpointDiff / 20.0); // reach it in 20 intervals, scaled to FPS
28981 hpForegroundValue = std::min(static_cast<real_t>(HPBar.animateSetpoint), hpForegroundValue);
28982
28983 if ( abs(HPBar.animateSetpoint) - abs(hpForegroundValue) <= 1.0 )
28984 {
28985 hpForegroundValue = HPBar.animateSetpoint;
28986 }
28987
28988 /* int increment = 3;
28989 double scaledIncrement = (increment * (getFPSScale(144.0)));*/
28990 //real_t diff = std::max(.1, (HPBar.animateSetpoint * 10 - hpForegroundValue) / (maxValue / 5)); // 0.1-5 value
28991 //if ( HPBar.animateSetpoint * 10 >= maxValue )
28992 //{
28993 // diff = 5;
28994 //}
28995 //scaledIncrement *= 0.2 * pow(diff, 2) + .5;
28996
28997 //hpForegroundValue = std::min(HPBar.animateSetpoint * 10.0, hpForegroundValue + scaledIncrement);
28998 //messagePlayer(0, "%.2f | %.2f", hpForegroundValue);
28999 }
29000 else if ( hpForegroundValue > HPBar.animateSetpoint ) // losing HP, snap to value
29001 {
29002 hpForegroundValue = HPBar.animateSetpoint;
29003 }
29004
29005 if ( hpFadedValue < HPBar.animateSetpoint )
29006 {
29007 hpFadedValue = hpForegroundValue;
29008 HPBar.animateTicks = ticks;
29009 }
29010 else if ( hpFadedValue > HPBar.animateSetpoint )
29011 {
29012 if ( ticks - HPBar.animateTicks > 30 /*|| stats[player.playernum]->HP <= 0*/ ) // fall after x ticks
29013 {
29014 real_t setpointDiff = std::max(0.01, hpFadedValue - HPBar.animateSetpoint);
29015 real_t fpsScale = getFPSScale(144.0);
29016 hpFadedValue -= fpsScale * (setpointDiff / 20.0); // reach it in 20 intervals, scaled to FPS
29017 hpFadedValue = std::max(static_cast<real_t>(HPBar.animateSetpoint), hpFadedValue);
29018 }
29019 }
29020 else
29021 {
29022 HPBar.animateTicks = ticks;
29023 }
29024
29025 char playerHPText[16];
29026 snprintf(playerHPText, sizeof(playerHPText), "%d", stats[player.playernum]->HP);
29027
29028 auto hpText = hpForegroundFrame->findField("hp text");
29029 hpText->setText(playerHPText);
29030
29031 real_t foregroundPercent = hpForegroundValue / HPBar.maxValue;
29032 hpProgress->pos.w = std::max(1, static_cast<int>((progressWidth) * foregroundPercent));
29033 hpProgressEndCap->pos.x = hpProgress->pos.x + hpProgress->pos.w;
29034
29035 real_t fadePercent = hpFadedValue / HPBar.maxValue;
29036 hpFaded->pos.w = std::max(1, static_cast<int>((progressWidth) * fadePercent));
29037 hpFadedEndCap->pos.x = hpFaded->pos.x + hpFaded->pos.w;
29038 if ( hpFaded->pos.w == 1 && stats[player.playernum]->HP <= 0 )
29039 {
29040 hpFaded->disabled = true;
29041 real_t opacity = hpFadeFrame->getOpacity();
29042 real_t opacityChange = .5 * getFPSScale(144.0); // change by .05% independant of fps
29043 hpFadeFrame->setOpacity(std::max(0.0, opacity - opacityChange));
29044
29045 // make this element fade out to the left, starting 54px then finally at 40px. @ 40px it's out of shot (6 width + 8 endcap width)
29046 hpFadedBase->pos.x = 54 - (14 * (1.0 - opacity / 100.0));
29047 hpFadedEndCap->pos.x = hpFadedBase->pos.x + hpFadedBase->pos.w;
29048 }
29049 else
29050 {
29051 hpFaded->disabled = false;
29052 hpFadeFrame->setOpacity(100.0);
29053
29054 // reset to 54px left as we altered in above if statement
29055 hpFadedBase->pos.x = 54;
29056 }
29057
29058 if ( stats[player.playernum]->HP <= 0 )
29059 {
29060 // hide all the progress elements when dead, as endcap/base don't shrink
29061 // hpProgress width 0px defaults to original size, so hide that too
29062 hpProgress->disabled = true;
29063 hpProgressEndCap->disabled = true;
29064 hpProgressBot->disabled = true;
29065 }
29066 else
29067 {
29068 hpProgress->disabled = false;
29069 hpProgressEndCap->disabled = false;
29070 hpProgressBot->disabled = false;
29071 }
29072
29073 // dividers
29074 {
29075 const int fullBarWidth = hpProgressBot->pos.w + progressWidth + hpEndcap->pos.w / 2;
29076 auto div25Percent = hpForegroundFrame->findImage("hp img div 25pc");
29077 div25Percent->disabled = false;
29078 div25Percent->pos.x = hpProgressBot->pos.x + fullBarWidth * .25 - 2;
29079 auto div50Percent = hpForegroundFrame->findImage("hp img div 50pc");
29080 div50Percent->disabled = false;
29081 div50Percent->pos.x = hpProgressBot->pos.x + fullBarWidth * .5 - 2;
29082 auto div75Percent = hpForegroundFrame->findImage("hp img div 75pc");
29083 div75Percent->disabled = false;
29084 div75Percent->pos.x = hpProgressBot->pos.x + fullBarWidth * .75 - 2;
29085
29086 if ( div50Percent->pos.x - div25Percent->pos.x < HPMPdividerThresholdInterval )
29087 {
29088 div75Percent->disabled = true;
29089 // 2 dividers 33%/66%
29090 div25Percent->pos.x = hpProgressBot->pos.x + fullBarWidth * .33 - 2;
29091 div50Percent->pos.x = hpProgressBot->pos.x + fullBarWidth * .66 - 2;
29092 if ( div50Percent->pos.x - div25Percent->pos.x < HPMPdividerThresholdInterval )
29093 {
29094 // 1 divider 50%
29095 div25Percent->disabled = true;
29096 div50Percent->pos.x = hpProgressBot->pos.x + fullBarWidth * .5 - 2;
29097 }
29098 }
29099 }
29100
29101 hpProgress->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPMid_00.png";
29102 hpProgressBot->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPBot_00.png";
29103 hpProgressEndCap->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPEnd_00.png";
29104 auto hpProgressEndCapFlash = hpForegroundFrame->findImage("hp img progress endcap flash");
29105 hpProgressEndCapFlash->disabled = true;
29106 const int framesPerAnimation = (HPBar.flashType == FLASH_ON_DAMAGE ? 1 : 2)/* * *cvar_hpanimdebug*/;
29107 const int numAnimationFrames = (HPBar.flashType == FLASH_ON_DAMAGE ? 20 : 2)/* * *cvar_hpanimdebug*/;
29108 if ( HPBar.flashTicks > 0 )
29109 {
29110 //messagePlayer(0, MESSAGE_DEBUG, "ticks: %d, animticks: %d, state: %d", ticks, HPBarFlashTicks, HPBarFlashAnimState);
29111 if ( HPBar.flashAnimState > numAnimationFrames || hpProgress->disabled )
29112 {
29113 HPBar.flashTicks = 0;
29114 HPBar.flashType = FLASH_ON_DAMAGE;
29115 HPBar.flashAnimState = -1;
29116 hpProgressEndCapFlash->disabled = true;
29117 }
29118 else
29119 {
29120 hpProgressEndCapFlash->disabled = hpProgressEndCap->disabled;
29121 if ( ticks == HPBar.flashTicks )
29122 {
29123 HPBar.flashAnimState = 1;
29124 HPBar.flashProcessedOnTick = ticks;
29125 }
29126 else if ( (HPBar.flashProcessedOnTick != ticks)
29127 && (ticks > HPBar.flashTicks)
29128 && (ticks - HPBar.flashTicks) % framesPerAnimation == 0 )
29129 {
29130 ++HPBar.flashAnimState;
29131 HPBar.flashProcessedOnTick = ticks;
29132 }
29133
29134 if ( HPBar.flashType == 0 )
29135 {
29136 if ( HPBar.flashAnimState <= 6 )
29137 {
29138 hpProgressEndCapFlash->color = 0xFFFFFFFF;
29139 }
29140
29141 if ( HPBar.flashAnimState == 0 )
29142 {
29143 hpProgress->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPMid_00.png";
29144 hpProgressEndCapFlash->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPEnd_F00.png";
29145 hpProgressBot->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPBot_00.png";
29146 }
29147 else if ( HPBar.flashAnimState >= 1 && HPBar.flashAnimState <= 2 )
29148 {
29149 hpProgress->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPMid_01.png";
29150 hpProgressEndCapFlash->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPEnd_F01.png";
29151 hpProgressBot->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPBot_01.png";
29152 }
29153 else if ( HPBar.flashAnimState == 3 )
29154 {
29155 hpProgress->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPMid_02.png";
29156 hpProgressEndCapFlash->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPEnd_F02.png";
29157 hpProgressBot->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPBot_02.png";
29158 }
29159 else if ( HPBar.flashAnimState >= 4 && HPBar.flashAnimState <= 5 )
29160 {
29161 hpProgress->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPMid_01.png";
29162 hpProgressEndCapFlash->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPEnd_F01.png";
29163 hpProgressBot->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPBot_01.png";
29164 }
29165 else if ( HPBar.flashAnimState == 6 )
29166 {
29167 hpProgress->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPMid_03.png";
29168 hpProgressEndCapFlash->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPEnd_F03.png";
29169 hpProgressBot->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPBot_03.png";
29170 }
29171 else
29172 {
29173 hpProgress->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPMid_00.png";
29174 hpProgressEndCapFlash->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPEnd_F04.png";
29175 Uint8 r, g, b, a;
29176 getColor(hpProgressEndCapFlash->color, &r, &g, &b, &a);
29177 int decrement = 20;
29178 real_t fpsScale = getFPSScale(60.0);
29179 decrement *= fpsScale;
29180 a = std::max(0, (int)a - decrement);
29181 hpProgressEndCapFlash->color = makeColor(r, g, b, a);
29182 }
29183 }
29184 else
29185 {
29186 hpProgressEndCapFlash->color = 0xFFFFFFFF;
29187 hpProgressEndCapFlash->disabled = true;
29188 if ( HPBar.flashAnimState == 1 )
29189 {
29190 hpProgress->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPMid_03.png";
29191 hpProgressBot->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPBot_03.png";
29192 hpProgressEndCap->path = "*#images/ui/HUD/hpmpbars/HUD_Bars_HPEnd_01.png";
29193 }
29194 }
29195 }
29196 }
29197 else
29198 {
29199 HPBar.flashAnimState = -1;
29200 hpProgressEndCapFlash->disabled = true;
29201 }
29202 {
29203 hpProgressEndCapFlash->pos.w = 22;
29204 hpProgressEndCapFlash->section.x = 0;
29205 hpProgressEndCapFlash->section.w = 0;
29206 if ( !hpProgressEndCap->disabled )
29207 {
29208 hpProgressEndCapFlash->pos.x = hpProgressEndCap->pos.x - (hpProgressEndCapFlash->pos.w - hpProgressEndCap->pos.w);
29209 }
29210 if ( hpProgressEndCapFlash->pos.x < hpProgressBot->pos.x )
29211 {
29212 // adjust end cap flash to clip correctly sliding past end of bar
29213 int overflowx = (hpProgressBot->pos.x - hpProgressEndCapFlash->pos.x);
29214 hpProgressEndCapFlash->section.x = (overflowx);
29215 hpProgressEndCapFlash->pos.x += overflowx;
29216 hpProgressEndCapFlash->pos.w -= overflowx;
29217 hpProgressEndCapFlash->section.w = hpProgressEndCapFlash->pos.w;
29218 }
29219 }
29220
29221 hpFrame->setDisabled(player.ghost.isActive());
29222}
29223
29224void Player::HUD_t::updateMPBar()
29225{
29226 if ( !mpFrame )
29227 {
29228 return;
29229 }
29230
29231 bool bCompactWidth = false;
29232 bool bCompactHeight = player.bUseCompactGUIHeight();
29233 if ( player.bUseCompactGUIWidth() || (keystatus[SDLK_t] && enableDebugKeys) )
29234 {
29235 bCompactWidth = true;
29236 }
29237
29238 SDL_Rect pos = mpFrame->getSize();
29239 pos.w = HPMP_FRAME_WIDTH + (bCompactWidth ? hpmpbarCompactOffsetWidth : hpmpbarOffsetWidth);
29240 if ( !player.bUseCompactGUIWidth()
29241 && player.bUseCompactGUIHeight() && *MainMenu::clipped_splitscreen
29242 && bShortHPMPForActionBars )
29243 {
29244 // shorten if action prompts visible in clipped wide mode
29245 pos.w -= kHPMPWidthReduce2pWideClippedActionPrompts;
29246 }
29247 pos.x = HPMP_FRAME_START_X + ((bCompactWidth || bCompactHeight) ? hpmpbarCompactOffsetX : hpmpbarOffsetX);
29248 pos.y = hudFrame->getSize().h - HPMP_FRAME_START_Y + HPMP_FRAME_HEIGHT + ((bCompactWidth || bCompactHeight) ? hpmpbarCompactOffsetY : hpmpbarOffsetY);
29249 pos.y -= player.hud.offsetHUDAboveHotbarHeight;
29250 mpFrame->setSize(pos);
29251
29252 auto mpForegroundFrame = mpFrame->findFrame("mp foreground frame");
29253 {
29254 auto _pos = mpForegroundFrame->getSize();
29255 _pos.w = pos.w;
29256 mpForegroundFrame->setSize(_pos);
29257 }
29258
29259 auto mpBg = mpFrame->findImage("mp img base");
29260 auto mpEndcap = mpForegroundFrame->findImage("mp img endcap");
29261 auto mpProgressBot = mpForegroundFrame->findImage("mp img progress bot");
29262 auto mpProgress = mpForegroundFrame->findImage("mp img progress");
29263 auto mpProgressEndCap = mpForegroundFrame->findImage("mp img progress endcap");
29264 auto mpFadeFrame = mpFrame->findFrame("mp fade frame");
29265 {
29266 auto _pos = mpFadeFrame->getSize();
29267 _pos.w = pos.w;
29268 mpFadeFrame->setSize(_pos);
29269 }
29270 auto mpFadedBase = mpFadeFrame->findImage("mp img fade bot");
29271 mpFadedBase->path = MPBarPaths_t::get(player.playernum, "mp img fade bot");
29272 auto mpFaded = mpFadeFrame->findImage("mp img fade");
29273 mpFaded->path = MPBarPaths_t::get(player.playernum, "mp img fade");
29274 auto mpFadedEndCap = mpFadeFrame->findImage("mp img fade endcap");
29275 mpFadedEndCap->path = MPBarPaths_t::get(player.playernum, "mp img fade endcap");
29276 auto mpBase = mpForegroundFrame->findImage("mp img value");
29277 mpBase->path = MPBarPaths_t::get(player.playernum, "mp img value");
29278
29279 real_t progressWidth = mpFrame->getSize().w - 74;
29280 int backgroundWidth = mpFrame->getSize().w - 54;
29281
29282 // handle bar size changing
29283 {
29284 real_t multiplier = 1.0;
29285 const Sint32 maxMPWidth = (bCompactWidth ? hpmpbarCompactMaxWidthAmount : hpmpbarMaxWidthAmount);
29286 if ( stats[player.playernum]->MAXMP < maxMPWidth )
29287 {
29288 // start at 30%, increase 2.5% every 5 MP past 20 MAXMP
29289 multiplier = (bCompactWidth ? hpmpbarCompactBasePercentSize : hpmpbarBasePercentSize) / 100.0;
29290 real_t widthIntervalPercent = (bCompactWidth ? hpmpbarCompactWidthIncreasePercentOnInterval : hpmpbarWidthIncreasePercentOnInterval) / 100.0;
29291 int intervalThreshold = (bCompactWidth ? hpmpbarCompactIntervalToIncreaseWidth : hpmpbarIntervalToIncreaseWidth);
29292 int baseIntervalStart = (bCompactWidth ? hpmpbarCompactIntervalStartValue : hpmpbarIntervalStartValue);
29293 multiplier += (widthIntervalPercent * ((std::max(0, stats[player.playernum]->MAXMP - baseIntervalStart) / intervalThreshold)));
29294 }
29295
29296 int diff = static_cast<int>(std::max(0.0, progressWidth - progressWidth * multiplier)); // how many pixels the progress bar shrinks
29297 progressWidth *= multiplier; // scale the progress bars
29298 mpBg->pos.w = backgroundWidth - diff; // move the background bar by x pixels as above
29299 mpEndcap->pos.x = mpFrame->getSize().w - mpEndcap->pos.w - diff; // move the background endcap by x pixels as above
29300 }
29301
29302 MPBar.animatePreviousSetpoint = MPBar.animateSetpoint;
29303 real_t& mpForegroundValue = MPBar.animateValue;
29304 real_t& mpFadedValue = MPBar.animateValue2;
29305
29306 MPBar.animateSetpoint = stats[player.playernum]->MP;
29307
29308 bool flashAnimationPreviouslyPlaying = MPBar.flashTicks > 0;
29309 if ( MPBar.animateSetpoint < MPBar.animatePreviousSetpoint ) // insta-change as losing health
29310 {
29311 mpForegroundValue = MPBar.animateSetpoint;
29312 MPBar.animateTicks = ticks;
29313
29314 // flash for taking damage
29315 MPBar.flashTicks = ticks;
29316 MPBar.flashProcessedOnTick = 0;
29317 MPBar.flashAnimState = -1;
29318 MPBar.flashType = FLASH_ON_DAMAGE;
29319 }
29320
29321 if ( MPBar.maxValue > stats[player.playernum]->MAXMP )
29322 {
29323 mpFadedValue = MPBar.animateSetpoint; // resetting game etc, stop fade animation sticking out of frame
29324 }
29325
29326 MPBar.maxValue = stats[player.playernum]->MAXMP;
29327 if ( mpForegroundValue < MPBar.animateSetpoint ) // gaining MP, animate
29328 {
29329 // flash for gaining MP, provided not already flashing
29330 /*if ( MPBar.flashAnimState == -1 || (MPBar.flashAnimState >= 0 && MPBar.flashType != FLASH_ON_DAMAGE) )
29331 {
29332 MPBar.flashTicks = ticks;
29333 MPBar.flashAnimState = -1;
29334 MPBar.flashType = FLASH_ON_RECOVERY;
29335 }*/
29336
29337 real_t setpointDiff = std::max(.1, MPBar.animateSetpoint - mpForegroundValue);
29338 real_t fpsScale = getFPSScale(144.0);
29339 mpForegroundValue += fpsScale * (setpointDiff / 20.0); // reach it in 20 intervals, scaled to FPS
29340 mpForegroundValue = std::min(static_cast<real_t>(MPBar.animateSetpoint), mpForegroundValue);
29341
29342 /*if ( abs(MPBar.animateSetpoint) - abs(mpForegroundValue) <= 1.0 )
29343 {
29344 mpForegroundValue = MPBar.animateSetpoint;
29345 }*/
29346 }
29347 else if ( mpForegroundValue > MPBar.animateSetpoint ) // losing MP, snap to value
29348 {
29349 mpForegroundValue = MPBar.animateSetpoint;
29350 }
29351
29352 if ( mpFadedValue < MPBar.animateSetpoint )
29353 {
29354 mpFadedValue = mpForegroundValue;
29355 MPBar.animateTicks = ticks;
29356 }
29357 else if ( mpFadedValue > MPBar.animateSetpoint )
29358 {
29359 if ( ticks - MPBar.animateTicks > 30 /*|| stats[player.playernum]->MP <= 0*/ ) // fall after x ticks
29360 {
29361 real_t setpointDiff = std::max(0.1, mpFadedValue - MPBar.animateSetpoint);
29362 real_t fpsScale = getFPSScale(144.0);
29363 mpFadedValue -= fpsScale * (setpointDiff / 20.0); // reach it in 20 intervals, scaled to FPS
29364 mpFadedValue = std::max(static_cast<real_t>(MPBar.animateSetpoint), mpFadedValue);
29365 }
29366 }
29367 else
29368 {
29369 MPBar.animateTicks = ticks;
29370 }
29371
29372 char playerMPText[16];
29373 snprintf(playerMPText, sizeof(playerMPText), "%d", stats[player.playernum]->MP);
29374
29375 auto mpText = mpForegroundFrame->findField("mp text");
29376 mpText->setText(playerMPText);
29377
29378 real_t foregroundPercent = mpForegroundValue / MPBar.maxValue;
29379 mpProgress->pos.w = std::max(1, static_cast<int>((progressWidth)* foregroundPercent));
29380 mpProgressEndCap->pos.x = mpProgress->pos.x + mpProgress->pos.w;
29381
29382 real_t fadePercent = mpFadedValue / MPBar.maxValue;
29383 mpFaded->pos.w = std::max(1, static_cast<int>((progressWidth)* fadePercent));
29384 mpFadedEndCap->pos.x = mpFaded->pos.x + mpFaded->pos.w;
29385 if ( mpFaded->pos.w == 1 && stats[player.playernum]->MP <= 0 )
29386 {
29387 mpFaded->disabled = true;
29388 real_t opacity = mpFadeFrame->getOpacity();
29389 real_t opacityChange = .5 * getFPSScale(144.0); // change by .05% independant of fps
29390 mpFadeFrame->setOpacity(std::max(0.0, opacity - opacityChange));
29391
29392 // make this element fade out to the left, starting 54px then finally at 40px. @ 40px it's out of shot (6 width + 8 endcap width)
29393 mpFadedBase->pos.x = 54 - (14 * (1.0 - opacity / 100.0));
29394 mpFadedEndCap->pos.x = mpFadedBase->pos.x + mpFadedBase->pos.w;
29395 }
29396 else
29397 {
29398 mpFaded->disabled = false;
29399 mpFadeFrame->setOpacity(100.0);
29400
29401 // reset to 54px left as we altered in above if statement
29402 mpFadedBase->pos.x = 54;
29403 }
29404
29405 if ( stats[player.playernum]->MP <= 0 )
29406 {
29407 // hide all the progress elements when dead, as endcap/base don't shrink
29408 // mpProgress width 0px defaults to original size, so hide that too
29409 mpProgress->disabled = true;
29410 mpProgressEndCap->disabled = true;
29411 mpProgressBot->disabled = true;
29412 }
29413 else
29414 {
29415 mpProgress->disabled = false;
29416 mpProgressEndCap->disabled = false;
29417 mpProgressBot->disabled = false;
29418 }
29419
29420 // dividers
29421 {
29422 const int fullBarWidth = mpProgressBot->pos.w + progressWidth + mpEndcap->pos.w / 2;
29423 auto div25Percent = mpForegroundFrame->findImage("mp img div 25pc");
29424 div25Percent->disabled = false;
29425 div25Percent->pos.x = mpProgressBot->pos.x + fullBarWidth * .25 - 2;
29426 auto div50Percent = mpForegroundFrame->findImage("mp img div 50pc");
29427 div50Percent->disabled = false;
29428 div50Percent->pos.x = mpProgressBot->pos.x + fullBarWidth * .5 - 2;
29429 auto div75Percent = mpForegroundFrame->findImage("mp img div 75pc");
29430 div75Percent->disabled = false;
29431 div75Percent->pos.x = mpProgressBot->pos.x + fullBarWidth * .75 - 2;
29432
29433 if ( div50Percent->pos.x - div25Percent->pos.x < HPMPdividerThresholdInterval )
29434 {
29435 div75Percent->disabled = true;
29436 // 2 dividers 33%/66%
29437 div25Percent->pos.x = mpProgressBot->pos.x + fullBarWidth * .33 - 2;
29438 div50Percent->pos.x = mpProgressBot->pos.x + fullBarWidth * .66 - 2;
29439 if ( div50Percent->pos.x - div25Percent->pos.x < HPMPdividerThresholdInterval )
29440 {
29441 // 1 divider 50%
29442 div25Percent->disabled = true;
29443 div50Percent->pos.x = mpProgressBot->pos.x + fullBarWidth * .5 - 2;
29444 }
29445 }
29446 }
29447
29448 mpProgress->path = MPBarPaths_t::get(player.playernum, "mp img progress");
29449 mpProgressBot->path = MPBarPaths_t::get(player.playernum, "mp img progress bot");
29450 mpProgressEndCap->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap");
29451 auto mpProgressEndCapFlash = mpForegroundFrame->findImage("mp img progress endcap flash");
29452 mpProgressEndCapFlash->disabled = true;
29453 const int framesPerAnimation = (MPBar.flashType == FLASH_ON_DAMAGE ? 1 : 2)/* * *cvar_hpanimdebug*/;
29454 const int numAnimationFrames = (MPBar.flashType == FLASH_ON_DAMAGE ? 30 : 2)/* * *cvar_hpanimdebug*/;
29455 if ( MPBar.flashTicks > 0 )
29456 {
29457 if ( MPBar.flashAnimState > numAnimationFrames || mpProgress->disabled )
29458 {
29459 MPBar.flashTicks = 0;
29460 MPBar.flashType = FLASH_ON_DAMAGE;
29461 MPBar.flashAnimState = -1;
29462 mpProgressEndCapFlash->disabled = true;
29463 }
29464 else
29465 {
29466 mpProgressEndCapFlash->disabled = mpProgressEndCap->disabled;
29467 bool processedOnTick = MPBar.flashProcessedOnTick == ticks;
29468 if ( ticks == MPBar.flashTicks )
29469 {
29470 MPBar.flashAnimState = 1;
29471 MPBar.flashProcessedOnTick = ticks;
29472
29473 if ( mpProgressEndCapFlash->path == MPBarPaths_t::get(player.playernum, "mp img progress endcap flash 1")
29474 || mpProgressEndCapFlash->path == MPBarPaths_t::get(player.playernum, "mp img progress endcap flash 2")
29475 || mpProgressEndCapFlash->path == MPBarPaths_t::get(player.playernum, "mp img progress endcap flash 3") )
29476 {
29477 mpProgressEndCapFlash->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap flash"); // reset to no full flash
29478 }
29479 }
29480 else if ( (!processedOnTick)
29481 && (ticks > MPBar.flashTicks)
29482 && (ticks - MPBar.flashTicks) % framesPerAnimation == 0 )
29483 {
29484 ++MPBar.flashAnimState;
29485 MPBar.flashProcessedOnTick = ticks;
29486 }
29487
29488 if ( MPBar.flashType == 0 )
29489 {
29490 if ( MPBar.flashAnimState <= 16 && MPBar.flashAnimState >= 10 )
29491 {
29492 mpProgressEndCapFlash->color = 0xFFFFFFFF;
29493 }
29494 else if ( MPBar.flashAnimState == 0 )
29495 {
29496 mpProgressEndCapFlash->color = makeColor(255, 255, 255, 0);
29497 mpProgressEndCapFlash->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap flash");
29498 }
29499
29500 if ( MPBar.flashAnimState <= 9 )
29501 {
29502 if ( MPBar.flashAnimState == 7 )
29503 {
29504 // we need the MP bar to flash long enough for long spellcast times
29505 // can adjust how many animStates we skip here to play with timing,
29506 // without changing other state machine code
29507 MPBar.flashAnimState = 9; // 1, 2 skip a few..
29508 }
29509 Uint8 r, g, b, a;
29510 getColor(mpProgressEndCapFlash->color, &r, &g, &b, &a);
29511 int increment = 10;
29512 real_t fpsScale = getFPSScale(60.0);
29513 increment *= fpsScale;
29514 a = std::min(255, (int)a + increment);
29515 mpProgressEndCapFlash->color = makeColor(r, g, b, a);
29516
29517 mpProgress->path = MPBarPaths_t::get(player.playernum, "mp img progress");
29518 mpProgressBot->path = MPBarPaths_t::get(player.playernum, "mp img progress bot");
29519
29520 if ( MPBar.flashAnimState % 2 == 0
29521 && !processedOnTick )
29522 {
29523 if ( mpProgressEndCapFlash->path == MPBarPaths_t::get(player.playernum, "mp img progress endcap flash") )
29524 {
29525 mpProgressEndCapFlash->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap flash b");
29526 }
29527 else if ( mpProgressEndCapFlash->path == MPBarPaths_t::get(player.playernum, "mp img progress endcap flash b") )
29528 {
29529 mpProgressEndCapFlash->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap flash c");
29530 }
29531 else if ( mpProgressEndCapFlash->path == MPBarPaths_t::get(player.playernum, "mp img progress endcap flash c") )
29532 {
29533 mpProgressEndCapFlash->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap flash d");
29534 }
29535 else
29536 {
29537 mpProgressEndCapFlash->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap flash");
29538 }
29539 }
29540 }
29541 else if ( MPBar.flashAnimState <= 10 )
29542 {
29543 mpProgress->path = MPBarPaths_t::get(player.playernum, "mp img progress");
29544 mpProgressEndCapFlash->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap flash");
29545 mpProgressBot->path = MPBarPaths_t::get(player.playernum, "mp img progress bot");
29546 }
29547 else if ( MPBar.flashAnimState >= 11 && MPBar.flashAnimState <= 12 )
29548 {
29549 mpProgress->path = MPBarPaths_t::get(player.playernum, "mp img progress 1");
29550 mpProgressEndCapFlash->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap flash 1");
29551 mpProgressBot->path = MPBarPaths_t::get(player.playernum, "mp img progress bot 1");
29552 }
29553 else if ( MPBar.flashAnimState == 13 )
29554 {
29555 mpProgress->path = MPBarPaths_t::get(player.playernum, "mp img progress 2");
29556 mpProgressEndCapFlash->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap flash 2");
29557 mpProgressBot->path = MPBarPaths_t::get(player.playernum, "mp img progress bot 2");
29558 }
29559 else if ( MPBar.flashAnimState >= 14 && MPBar.flashAnimState <= 15 )
29560 {
29561 mpProgress->path = MPBarPaths_t::get(player.playernum, "mp img progress 1");
29562 mpProgressEndCapFlash->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap flash 1");
29563 mpProgressBot->path = MPBarPaths_t::get(player.playernum, "mp img progress bot 1");
29564 }
29565 else if ( MPBar.flashAnimState == 16 )
29566 {
29567 mpProgress->path = MPBarPaths_t::get(player.playernum, "mp img progress 3");
29568 mpProgressEndCapFlash->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap flash 3");
29569 mpProgressBot->path = MPBarPaths_t::get(player.playernum, "mp img progress bot 3");
29570 }
29571 else
29572 {
29573 mpProgress->path = MPBarPaths_t::get(player.playernum, "mp img progress");
29574 mpProgressEndCapFlash->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap flash 4");
29575 Uint8 r, g, b, a;
29576 getColor(mpProgressEndCapFlash->color, &r, &g, &b, &a);
29577 int decrement = 20;
29578 real_t fpsScale = getFPSScale(60.0);
29579 decrement *= fpsScale;
29580 a = std::max(0, (int)a - decrement);
29581 mpProgressEndCapFlash->color = makeColor(r, g, b, a);
29582 }
29583 }
29584 else
29585 {
29586 mpProgressEndCapFlash->color = 0xFFFFFFFF;
29587 mpProgressEndCapFlash->disabled = true;
29588 if ( MPBar.flashAnimState == 1 )
29589 {
29590 mpProgress->path = MPBarPaths_t::get(player.playernum, "mp img progress 3");
29591 mpProgressBot->path = MPBarPaths_t::get(player.playernum, "mp img progress bot 3");
29592 mpProgressEndCap->path = MPBarPaths_t::get(player.playernum, "mp img progress endcap 1");
29593 }
29594 }
29595 }
29596 }
29597 else
29598 {
29599 MPBar.flashAnimState = -1;
29600 mpProgressEndCapFlash->disabled = true;
29601 }
29602
29603 {
29604 mpProgressEndCapFlash->pos.w = 22;
29605 mpProgressEndCapFlash->section.x = 0;
29606 mpProgressEndCapFlash->section.w = 0;
29607 if ( !mpProgressEndCap->disabled )
29608 {
29609 mpProgressEndCapFlash->pos.x = mpProgressEndCap->pos.x - (mpProgressEndCapFlash->pos.w - mpProgressEndCap->pos.w);
29610 }
29611 if ( mpProgressEndCapFlash->pos.x < mpProgressBot->pos.x )
29612 {
29613 // adjust end cap flash to clip correctly sliding past end of bar
29614 int overflowx = (mpProgressBot->pos.x - mpProgressEndCapFlash->pos.x);
29615 mpProgressEndCapFlash->section.x = (overflowx);
29616 mpProgressEndCapFlash->pos.x += overflowx;
29617 mpProgressEndCapFlash->pos.w -= overflowx;
29618 mpProgressEndCapFlash->section.w = mpProgressEndCapFlash->pos.w;
29619 }
29620 }
29621
29622 if ( player.magic.noManaProcessedOnTick != 0 )
29623 {
29624 if ( ticks != player.magic.noManaProcessedOnTick )
29625 {
29626 ++player.magic.noManaFeedbackTicks;
29627 player.magic.noManaProcessedOnTick = ticks;
29628 }
29629 if ( player.magic.noManaFeedbackTicks > TICKS_PER_SECOND50 )
29630 {
29631 player.magic.noManaProcessedOnTick = 0;
29632 player.magic.noManaFeedbackTicks = 0;
29633 }
29634 }
29635
29636 mpFrame->setDisabled(player.ghost.isActive());
29637}
29638
29639bool hotbar_slot_t::matchesExactLastItem(int player, Item* item)
29640{
29641 if ( !item ) { return false; }
29642 if ( lastItem.uid == item->uid ) { return true; }
29643 if ( lastItem.type == item->type
29644 && lastItem.status == item->status
29645 && lastItem.count == item->count
29646 && lastItem.identified == item->identified )
29647 {
29648 if ( item->shouldItemStack(player, true) )
29649 {
29650 return true;
29651 }
29652 else
29653 {
29654 return lastItem.appearance == item->appearance;
29655 }
29656 }
29657 return false;
29658}
29659
29660void hotbar_slot_t::resetLastItem()
29661{
29662 lastCategory = -1;
29663 lastItem.type = WOODEN_SHIELD;
29664 lastItem.appearance = -1;
29665 lastItem.status = BROKEN;
29666 lastItem.count = 0;
29667 lastItem.identified = false;
29668 lastItem.uid = 0;
29669}
29670
29671void hotbar_slot_t::storeLastItem(Item* item)
29672{
29673 if ( !item ) { return; }
29674 if ( !item->identified ) { return; }
29675 lastItem.type = item->type;
29676 lastItem.appearance = item->appearance;
29677 lastItem.status = item->status;
29678 lastItem.count = item->count;
29679 lastItem.identified = item->identified;
29680 lastItem.uid = item->uid;
29681 lastCategory = itemCategory(item);
29682}
29683
29684std::string hotbarSlotBindingText(const int player, const int slotnum, const Input::binding_t& binding)
29685{
29686 std::string inputName = "";
29687 if ( binding.type == Input::binding_t::KEYBOARD && binding.keycode != SDLK_UNKNOWN )
29688 {
29689 if ( binding.keycode >= SDLK_0 && binding.keycode <= SDLK_9 )
29690 {
29691 inputName = SDL_GetKeyName(binding.keycode);
29692 }
29693 }
29694 else if ( binding.type != Input::binding_t::MOUSE_BUTTON )
29695 {
29696 if ( slotnum + 1 == 10 )
29697 {
29698 inputName = "0";
29699 }
29700 else
29701 {
29702 inputName = std::to_string(slotnum + 1);
29703 }
29704 }
29705 return inputName;
29706}
29707
29708void Player::Hotbar_t::updateHotbar()
29709{
29710 if ( !hotbarFrame )
29711 {
29712 return;
29713 }
29714
29715 hotbarFrame->setOpacity(hotbarFrame->getParent()->getOpacity());
29716 bool tempHideHotbar = false;
29717 if ( player.ghost.isActive() )
29718 {
29719 tempHideHotbar = true;
29720 }
29721 else if ( player.bUseCompactGUIHeight()
29722 && (player.gui_mode == GUI_MODE_FOLLOWERMENU
29723 || player.gui_mode == GUI_MODE_CALLOUT
29724 || (player.hud.compactLayoutMode == Player::HUD_t::COMPACT_LAYOUT_CHARSHEET && !player.shootmode)
29725 || (player.gui_mode == GUI_MODE_MAGIC)
29726 || (player.shopGUI.bOpen)
29727 || (player.inventoryUI.chestGUI.bOpen)
29728 || player.minimap.mapWindow
29729 || player.messageZone.logWindow
29730 || (GenericGUI[player.playernum].isGUIOpen())
29731 ) )
29732 {
29733 tempHideHotbar = true;
29734 }
29735 else if ( (!player.bUseCompactGUIHeight() && player.bUseCompactGUIWidth())
29736 && (player.hud.compactLayoutMode == Player::HUD_t::COMPACT_LAYOUT_CHARSHEET && !player.shootmode) )
29737 {
29738 tempHideHotbar = true;
29739 }
29740 if ( tempHideHotbar )
29741 {
29742 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
29743 real_t setpointDiff = fpsScale * std::max(.1, (1.0 - animHide)) / 2.5;
29744 animHide += setpointDiff;
29745 animHide = std::min(1.0, animHide);
29746 isInteractable = true;
29747 }
29748 else
29749 {
29750 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
29751 real_t setpointDiff = fpsScale * std::max(.1, (animHide)) / 2.5;
29752 animHide -= setpointDiff;
29753 animHide = std::max(0.0, animHide);
29754 if ( animHide <= 0.001 )
29755 {
29756 isInteractable = true;
29757 }
29758 }
29759
29760 if ( player.bUseCompactGUIHeight() && player.bUseCompactGUIWidth()
29761 && !player.shootmode && player.GUI.activeModule != Player::GUI_t::MODULE_HOTBAR )
29762 {
29763 hotbarFrame->setOpacity(75.0);
29764 }
29765
29766 bool bCompactView = false;
29767 bool loweredY = false;
29768 int compactDisableLeftRightOffsetX = 0;
29769 if ( (keystatus[SDLK_u] && enableDebugKeys) || player.bUseCompactGUIWidth() )
29770 {
29771 if ( !*cvar_hotbar_compact_disable )
29772 {
29773 bCompactView = true;
29774 }
29775 else
29776 {
29777 compactDisableLeftRightOffsetX = 8;
29778 }
29779 loweredY = true;
29780 }
29781 else if ( player.bUseCompactGUIHeight() )
29782 {
29783 loweredY = true;
29784 }
29785 int hotbarStartY1 = hotbarFrame->getSize().h + getHotbarStartY1(); // higher row (center group)
29786 int hotbarStartY2 = hotbarFrame->getSize().h + getHotbarStartY2(); // lower row (left/right)
29787 hotbarStartY1 += ((bCompactView || loweredY) ? hotbarCompactOffsetY : hotbarOffsetY);
29788 hotbarStartY2 += ((bCompactView || loweredY) ? hotbarCompactOffsetY : hotbarOffsetY);
29789
29790 static ConsoleVariable<int> cvar_hotbar_splitscreen_center_x("/hotbar_splitscreen_center_x", 0);
29791 const int hotbarCentreX = (hotbarFrame->getSize().w / 2) + *cvar_hotbar_splitscreen_center_x;
29792 int hotbarCentreXLeft = hotbarCentreX - 148 + (bCompactView ? hotbarCompactOffsetX : compactDisableLeftRightOffsetX);
29793 int hotbarCentreXRight = hotbarCentreX + 148 - (bCompactView ? hotbarCompactOffsetX : compactDisableLeftRightOffsetX);
29794 hotbarStartY1 += animHide * abs(getHotbarStartY1());
29795 hotbarStartY2 += animHide * abs(getHotbarStartY1());
29796
29797 if ( !player.shootmode || FollowerMenu[player.playernum].followerMenuIsOpen()
29798 || CalloutMenu[player.playernum].calloutMenuIsOpen() )
29799 {
29800 if (player.hotbar.useHotbarFaceMenu)
29801 {
29802 if ( Input::inputs[player.playernum].binaryToggle("Hotbar Down / Cancel") )
29803 {
29804 Input::inputs[player.playernum].consumeBinaryToggle("Hotbar Down / Cancel");
29805 }
29806 if ( Input::inputs[player.playernum].binaryToggle("Hotbar Left") )
29807 {
29808 Input::inputs[player.playernum].consumeBinaryToggle("Hotbar Left");
29809 }
29810 if ( Input::inputs[player.playernum].binaryToggle("Hotbar Up / Select") )
29811 {
29812 Input::inputs[player.playernum].consumeBinaryToggle("Hotbar Up / Select");
29813 }
29814 if ( Input::inputs[player.playernum].binaryToggle("Hotbar Right") )
29815 {
29816 Input::inputs[player.playernum].consumeBinaryToggle("Hotbar Right");
29817 }
29818 }
29819 faceMenuButtonHeld = FaceMenuGroup::GROUP_NONE;
29820 }
29821
29822 bool faceMenuSnapCursorInstantly = false;
29823 if ( faceMenuButtonHeld != FaceMenuGroup::GROUP_NONE || (player.GUI.activeModule == Player::GUI_t::MODULE_HOTBAR && !player.shootmode) )
29824 {
29825 if ( selectedSlotAnimateCurrentValue == 0.0 )
29826 {
29827 faceMenuSnapCursorInstantly = true;
29828 }
29829 real_t fpsScale = getFPSScale(144.0);
29830 real_t setpointDiff = std::max(0.1, 1.0 - selectedSlotAnimateCurrentValue);
29831 selectedSlotAnimateCurrentValue += fpsScale * (setpointDiff / 10.0);
29832 selectedSlotAnimateCurrentValue = std::min(1.0, selectedSlotAnimateCurrentValue);
29833 }
29834 else
29835 {
29836 selectedSlotAnimateCurrentValue = 0.0;
29837 }
29838
29839 auto highlightSlot = hotbarFrame->findFrame("hotbar highlight");
29840 auto highlightSlotImg = highlightSlot->findImage("highlight img");
29841 highlightSlotImg->disabled = true;
29842 auto highlightNumText = highlightSlot->findField("slot num text");
29843 highlightNumText->setDisabled(true);
29844
29845 auto shootmodeSelectedSlotCursor = hotbarFrame->findFrame("shootmode selected item cursor");
29846 if ( shootmodeSelectedSlotCursor )
29847 {
29848 shootmodeSelectedSlotCursor->setDisabled(true);
29849 }
29850
29851 if ( player.shootmode || !inputs.getUIInteraction(player.playernum)->selectedItem )
29852 {
29853 if ( auto oldSelectedItemFrame = hotbarFrame->findFrame("hotbar old selected item") )
29854 {
29855 oldSelectedItemFrame->setDisabled(true);
29856 }
29857 }
29858
29859 auto cancelPromptTxt = hotbarFrame->findField("hotbar cancel prompt");
29860 cancelPromptTxt->setDisabled(true);
29861 auto cancelPromptGlyph = hotbarFrame->findImage("hotbar cancel glyph");
29862 cancelPromptGlyph->disabled = true;
29863
29864 if ( (!bCompactView && !loweredY) && useHotbarFaceMenu && faceMenuButtonHeld != FaceMenuGroup::GROUP_NONE )
29865 {
29866 cancelPromptTxt->setDisabled(false);
29867 cancelPromptTxt->setText(Language::get(3063));
29868 static ConsoleVariable<int> cvar_hotbar_cancel_prompt_y("/hotbar_cancel_prompt_y", -6);
29869 static ConsoleVariable<int> cvar_hotbar_cancel_prompt_x("/hotbar_cancel_prompt_x", 29);
29870 cancelPromptGlyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("Hotbar Down / Cancel");
29871 if ( auto imgGet = Image::get(cancelPromptGlyph->path.c_str()) )
29872 {
29873 cancelPromptGlyph->pos.w = imgGet->getWidth();
29874 cancelPromptGlyph->pos.h = imgGet->getHeight();
29875 cancelPromptGlyph->disabled = false;
29876 }
29877 SDL_Rect promptTxtPos = cancelPromptTxt->getSize();
29878 promptTxtPos.x = hotbarCentreX + *cvar_hotbar_cancel_prompt_x;
29879 promptTxtPos.y = hotbarStartY1 + getSlotSize() - 2;
29880 if ( auto textGet = cancelPromptTxt->getTextObject() )
29881 {
29882 promptTxtPos.x -= textGet->getWidth() / 2;
29883 promptTxtPos.x += cancelPromptGlyph->pos.w / 2 + 2;
29884 if ( promptTxtPos.x % 2 == 1 )
29885 {
29886 ++promptTxtPos.x;
29887 }
29888 }
29889 cancelPromptGlyph->pos.y = promptTxtPos.y + *cvar_hotbar_cancel_prompt_y;
29890 cancelPromptGlyph->pos.x = promptTxtPos.x - 4 - cancelPromptGlyph->pos.w;
29891 cancelPromptTxt->setSize(promptTxtPos);
29892 }
29893
29894 // position the slots
29895 for ( int num = 0; num < NUM_HOTBAR_SLOTS; ++num )
29896 {
29897 if ( hotbar[num].item != 0 )
29898 {
29899 if ( Item* item = uidToItem(hotbar[num].item) )
29900 {
29901 if ( hotbar[num].item == hotbar[num].lastItem.uid
29902 && hotbar[num].lastItem.status > BROKEN
29903 && item->status == BROKEN )
29904 {
29905 // de-hotbar newly broken stuff
29906 hotbar[num].item = 0;
29907 hotbar[num].resetLastItem();
29908 }
29909 else
29910 {
29911 hotbar[num].storeLastItem(item);
29912 }
29913 }
29914 }
29915
29916 auto slot = getHotbarSlotFrame(num);
29917 assert(slot)(static_cast<void> (0));
29918
29919 if ( auto img = slot->findImage("slot img") ) // apply any opacity from config
29920 {
29921 Uint8 r, g, b, a;
29922 getColor(img->color, &r, &g, &b, &a);
29923 a = hotbarSlotOpacity;
29924 img->color = makeColor( r, g, b, a);
29925 }
29926 if ( highlightSlotImg )
29927 {
29928 Uint8 r, g, b, a;
29929 getColor(highlightSlotImg->color, &r, &g, &b, &a);
29930 a = hotbarSelectedSlotOpacity;
29931 highlightSlotImg->color = makeColor( r, g, b, a);
29932 }
29933
29934 char glyphname[32];
29935 snprintf(glyphname, sizeof(glyphname), "hotbar glyph %d", num);
29936 auto glyph = hotbarFrame->findImage(glyphname);
29937 assert(glyph)(static_cast<void> (0));
29938 glyph->disabled = true;
29939
29940 if ( useHotbarFaceMenu && num == 9 )
29941 {
29942 slot->setDisabled(true);
29943 }
29944 else
29945 {
29946 slot->setDisabled(false);
29947 }
29948
29949 SDL_Rect pos = slot->getSize();
29950 int compactViewOffset = 0;
29951 int compactInactiveSlotOffset = 0;
29952 int compactExpandedOffsetX = 0;
29953 int centreXLeft = hotbarCentreXLeft;
29954 int centreXRight = hotbarCentreXRight;
29955 int centreX = hotbarCentreX;
29956 bool compactViewNavigation = (player.GUI.activeModule == Player::GUI_t::MODULE_HOTBAR && !player.shootmode)
29957 || *cvar_hotbar_compact_use_fullsize;
29958 if ( bCompactView )
29959 {
29960 if ( compactViewNavigation )
29961 {
29962 compactViewOffset = pos.w * hotbarCompactSlotOverlapPercent / 2.0;
29963
29964 compactExpandedOffsetX = hotbarCompactExpandedOffsetX;
29965 centreXLeft -= compactExpandedOffsetX;
29966 centreXRight += compactExpandedOffsetX;
29967 }
29968 else
29969 {
29970 compactViewOffset = pos.w * hotbarCompactSlotOverlapPercent;
29971 }
29972 compactInactiveSlotOffset = (hotbarCompactInactiveSlotMovementX + (*cvar_hotbar_compact_use_fullsize ? -6 : 0))
29973 * selectedSlotAnimateCurrentValue;
29974
29975 if ( faceMenuButtonHeld != FaceMenuGroup::GROUP_NONE )
29976 {
29977 if ( faceMenuButtonHeld == FaceMenuGroup::GROUP_LEFT )
29978 {
29979 centreX += compactInactiveSlotOffset;
29980 centreXRight += compactInactiveSlotOffset;
29981 }
29982 else if( faceMenuButtonHeld == FaceMenuGroup::GROUP_MIDDLE )
29983 {
29984 centreXLeft -= compactInactiveSlotOffset;
29985 centreXRight += compactInactiveSlotOffset;
29986 }
29987 else if ( faceMenuButtonHeld == FaceMenuGroup::GROUP_RIGHT )
29988 {
29989 centreXLeft -= compactInactiveSlotOffset;
29990 centreX -= compactInactiveSlotOffset;
29991 }
29992 }
29993 }
29994 pos.x = centreX;
29995 pos.y = hotbarStartY2;
29996
29997 int slotYMovement = pos.h / 4;
29998
29999 auto slotItem = slot->findFrame("hotbar slot item");
30000 slotItem->setDisabled(true);
30001
30002 if ( useHotbarFaceMenu )
30003 {
30004 GameController* controller = inputs.getController(player.playernum);
30005 if ( controller )
30006 {
30007 glyph->disabled = slot->isDisabled();
30008 if ( !player.shootmode || !player.entity )
30009 {
30010 glyph->disabled = true;
30011 }
30012 }
30013
30014 slot->findField("slot num text")->setDisabled(true); // disable the hotkey prompts per slot
30015 switch ( num )
30016 {
30017 // left group
30018 case 0:
30019 pos.x = centreXLeft - pos.w / 2 - pos.w + 2 + compactViewOffset;
30020 if ( faceMenuButtonHeld == FaceMenuGroup::GROUP_LEFT )
30021 {
30022 pos.y -= slotYMovement * selectedSlotAnimateCurrentValue;
30023 pos.x -= compactViewOffset * selectedSlotAnimateCurrentValue;
30024 }
30025 else
30026 {
30027 glyph->disabled = true;
30028 }
30029 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("HotbarFacebarModifierLeft");
30030 break;
30031 case 1:
30032 pos.x = centreXLeft - pos.w / 2;
30033 pos.y -= slotYMovement;
30034 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("Hotbar Left",
30035 faceMenuButtonHeld == FaceMenuGroup::GROUP_LEFT);
30036 if ( faceMenuButtonHeld != FaceMenuGroup::GROUP_LEFT
30037 && faceMenuButtonHeld != FaceMenuGroup::GROUP_NONE )
30038 {
30039 glyph->color = makeColor(255, 255, 255, 255 * (1.0 - selectedSlotAnimateCurrentValue / 2));
30040 //glyph->disabled = true;
30041 }
30042 else
30043 {
30044 glyph->color = 0xFFFFFFFF;
30045 }
30046 break;
30047 case 2:
30048 pos.x = centreXLeft + (pos.w / 2 - 2) - compactViewOffset;
30049 if ( faceMenuButtonHeld == FaceMenuGroup::GROUP_LEFT )
30050 {
30051 pos.y -= slotYMovement * selectedSlotAnimateCurrentValue;
30052 pos.x += compactViewOffset * selectedSlotAnimateCurrentValue;
30053 }
30054 else
30055 {
30056 glyph->disabled = true;
30057 }
30058 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("HotbarFacebarModifierRight");
30059 break;
30060 // middle group
30061 case 3:
30062 pos.y = hotbarStartY1;
30063 pos.x = centreX - pos.w / 2 - pos.w + 2 + compactViewOffset;
30064 if ( faceMenuButtonHeld == FaceMenuGroup::GROUP_MIDDLE )
30065 {
30066 pos.y -= slotYMovement * selectedSlotAnimateCurrentValue;
30067 pos.x -= compactViewOffset * selectedSlotAnimateCurrentValue;
30068 }
30069 else
30070 {
30071 glyph->disabled = true;
30072 }
30073 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("HotbarFacebarModifierLeft");
30074 break;
30075 case 4:
30076 pos.y = hotbarStartY1;
30077 pos.y -= slotYMovement;
30078 pos.x = centreX - pos.w / 2;
30079 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("Hotbar Up / Select",
30080 faceMenuButtonHeld == FaceMenuGroup::GROUP_MIDDLE);
30081 if ( faceMenuButtonHeld != FaceMenuGroup::GROUP_MIDDLE
30082 && faceMenuButtonHeld != FaceMenuGroup::GROUP_NONE )
30083 {
30084 glyph->color = makeColor(255, 255, 255, 255 * (1.0 - selectedSlotAnimateCurrentValue / 2));
30085 //glyph->disabled = true;
30086 }
30087 else
30088 {
30089 glyph->color = 0xFFFFFFFF;
30090 }
30091 break;
30092 case 5:
30093 pos.y = hotbarStartY1;
30094 pos.x = centreX + (pos.w / 2 - 2) - compactViewOffset;
30095 if ( faceMenuButtonHeld == FaceMenuGroup::GROUP_MIDDLE )
30096 {
30097 pos.y -= slotYMovement * selectedSlotAnimateCurrentValue;
30098 pos.x += compactViewOffset * selectedSlotAnimateCurrentValue;
30099 }
30100 else
30101 {
30102 glyph->disabled = true;
30103 }
30104 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("HotbarFacebarModifierRight");
30105 break;
30106 // right group
30107 case 6:
30108 pos.x = centreXRight - pos.w / 2 - pos.w + 2 + compactViewOffset;
30109 if ( faceMenuButtonHeld == FaceMenuGroup::GROUP_RIGHT )
30110 {
30111 pos.y -= slotYMovement * selectedSlotAnimateCurrentValue;
30112 pos.x -= compactViewOffset * selectedSlotAnimateCurrentValue;
30113 }
30114 else
30115 {
30116 glyph->disabled = true;
30117 }
30118 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("HotbarFacebarModifierLeft");
30119 break;
30120 case 7:
30121 pos.x = centreXRight - pos.w / 2;
30122 pos.y -= slotYMovement;
30123 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("Hotbar Right",
30124 faceMenuButtonHeld == FaceMenuGroup::GROUP_RIGHT);
30125 if ( faceMenuButtonHeld != FaceMenuGroup::GROUP_RIGHT
30126 && faceMenuButtonHeld != FaceMenuGroup::GROUP_NONE )
30127 {
30128 glyph->color = makeColor(255, 255, 255, 255 * (1.0 - selectedSlotAnimateCurrentValue / 2));
30129 //glyph->disabled = true;
30130 }
30131 else
30132 {
30133 glyph->color = 0xFFFFFFFF;
30134 }
30135 break;
30136 case 8:
30137 pos.x = centreXRight + (pos.w / 2 - 2) - compactViewOffset;
30138 if ( faceMenuButtonHeld == FaceMenuGroup::GROUP_RIGHT )
30139 {
30140 pos.y -= slotYMovement * selectedSlotAnimateCurrentValue;
30141 pos.x += compactViewOffset * selectedSlotAnimateCurrentValue;
30142 }
30143 else
30144 {
30145 glyph->disabled = true;
30146 }
30147 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("HotbarFacebarModifierRight");
30148 break;
30149 default:
30150 break;
30151 }
30152
30153 slot->setSize(pos);
30154
30155 auto glyphImage = Image::get(glyph->path.c_str());
30156 if ( glyphImage )
30157 {
30158 glyph->pos.w = std::min((int)glyphImage->getWidth(), slot->getSize().w);
30159 glyph->pos.h = glyphImage->getHeight();
30160 glyph->pos.x = pos.x + pos.w / 2 - glyph->pos.w / 2;
30161 glyph->pos.y = pos.y - glyph->pos.h;
30162 }
30163 }
30164 else
30165 {
30166 auto slot_text = slot->findField("slot num text");
30167 slot_text->setDisabled(false); // enable the hotkey prompts per slot
30168 std::string slotstr = "Hotbar Slot " + std::to_string(num + 1);
30169 auto binding = Input::inputs[player.playernum].input(slotstr.c_str());
30170 std::string inputName = hotbarSlotBindingText(player.playernum, num, binding);
30171
30172 const unsigned int midpoint = NUM_HOTBAR_SLOTS / 2;
30173 if ( num < midpoint )
30174 {
30175 pos.x -= (pos.w) * (midpoint - num);
30176 }
30177 else
30178 {
30179 pos.x += (pos.w) * (num - midpoint);
30180 }
30181
30182 if ( inputName == "" )
30183 {
30184 slot_text->setText("");
30185 glyph->disabled = slot->isDisabled();
30186 glyph->color = 0xFFFFFFFF;// player.shootmode ? 0xFFFFFFFF : makeColor(255, 255, 255, 192);
30187 if ( binding.type == Input::binding_t::KEYBOARD )
30188 {
30189 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding(slotstr.c_str(), true);
30190 }
30191 else
30192 {
30193 glyph->path = Input::inputs[player.playernum].getGlyphPathForBinding(slotstr.c_str());
30194 }
30195 }
30196 else
30197 {
30198 slot_text->setText(inputName.c_str());
30199 }
30200
30201 slot->setSize(pos);
30202
30203 auto glyphImage = Image::get(glyph->path.c_str());
30204 if ( glyphImage )
30205 {
30206 glyph->pos.w = std::min((int)glyphImage->getWidth(), slot->getSize().w);
30207 glyph->pos.h = glyphImage->getHeight();
30208 glyph->pos.x = pos.x + pos.w / 2 - glyph->pos.w / 2;
30209 glyph->pos.y = pos.y - glyph->pos.h;
30210
30211 if ( binding.type == Input::binding_t::MOUSE_BUTTON )
30212 {
30213 glyph->pos.y += 4;
30214 }
30215 }
30216 }
30217
30218 if ( current_hotbar == num )
30219 {
30220 bool showHighlightedSlot = true;
30221 if ( players[player.playernum]->GUI.activeModule != Player::GUI_t::MODULE_HOTBAR
30222 && player.inventoryUI.frame && !player.inventoryUI.frame->isDisabled() )
30223 {
30224 // if inventory visible, don't show selection if navigating within inventory
30225 showHighlightedSlot = false;
30226 }
30227 else if ( player.hotbar.useHotbarFaceMenu && inputs.getVirtualMouse(player.playernum)->lastMovementFromController
30228 && player.hotbar.faceMenuButtonHeld == Player::Hotbar_t::FaceMenuGroup::GROUP_NONE
30229 && player.inventoryUI.frame && player.inventoryUI.frame->isDisabled() )
30230 {
30231 // if inventory invisible, don't show selection if face button not held
30232 showHighlightedSlot = false;
30233 }
30234
30235 auto highlightSlotItem = highlightSlot->findFrame("hotbar slot item");
30236 highlightSlotItem->setDisabled(true);
30237
30238 if ( showHighlightedSlot )
30239 {
30240 auto slotNumText = slot->findField("slot num text");
30241
30242 highlightNumText->setText(slotNumText->getText());
30243 highlightNumText->setDisabled(slotNumText->isDisabled());
30244
30245 highlightSlot->setSize(pos); // this follows the slots around
30246 highlightSlotImg->disabled = false;
30247 updateSlotFrameFromItem(highlightSlotItem, uidToItem(hotbar[num].item));
30248
30249 if ( player.inventoryUI.frame )
30250 {
30251 bool showCursor = true;
30252 if ( !player.shootmode )
30253 {
30254 if ( inputs.getUIInteraction(player.playernum)->itemMenuOpen &&
30255 inputs.getUIInteraction(player.playernum)->itemMenuFromHotbar )
30256 {
30257 showCursor = true;
30258 }
30259 else if ( inputs.getUIInteraction(player.playernum)->selectedItem
30260 && !highlightSlot->capturesMouseInRealtimeCoords() )
30261 {
30262 showCursor = false;
30263 }
30264 else if ( !inputs.getUIInteraction(player.playernum)->selectedItem
30265 && !inputs.getVirtualMouse(player.playernum)->lastMovementFromController
30266 && !highlightSlot->capturesMouse() )
30267 {
30268 showCursor = false;
30269 }
30270 }
30271 else if ( player.shootmode )
30272 {
30273 showCursor = true;
30274 }
30275
30276 if ( showCursor )
30277 {
30278 if ( !player.shootmode )
30279 {
30280 if ( players[player.playernum]->inventoryUI.selectedItemCursorFrame )
30281 {
30282 players[player.playernum]->inventoryUI.selectedItemCursorFrame->setDisabled(false);
30283 player.inventoryUI.updateSelectedSlotAnimation(pos.x - 1, pos.y - 1, getSlotSize() - 2, getSlotSize() - 2,
30284 inputs.getVirtualMouse(player.playernum)->draw_cursor);
30285 }
30286 }
30287 else if ( player.shootmode )
30288 {
30289 if ( shootmodeSelectedSlotCursor )
30290 {
30291 shootmodeSelectedSlotCursor->setDisabled(false);
30292 bool snapCursor = !inputs.getVirtualMouse(player.playernum)->lastMovementFromController;
30293 if ( useHotbarFaceMenu && faceMenuSnapCursorInstantly )
30294 {
30295 snapCursor = true;
30296 }
30297 updateSelectedSlotAnimation(pos.x - 1, pos.y - 1, getSlotSize() - 2, getSlotSize() - 2, snapCursor);
30298 }
30299 }
30300 }
30301 else
30302 {
30303 highlightSlotImg->disabled = true;
30304 highlightSlotItem->setDisabled(true);
30305 updateSlotFrameFromItem(slotItem, uidToItem(hotbar[num].item));
30306 }
30307 }
30308 }
30309 else
30310 {
30311 updateSlotFrameFromItem(slotItem, uidToItem(hotbar[num].item));
30312 }
30313 }
30314 else
30315 {
30316 updateSlotFrameFromItem(slotItem, uidToItem(hotbar[num].item));
30317 }
30318 }
30319}
30320
30321bool Player::Hotbar_t::warpMouseToHotbar(const int hotbarSlot, Uint32 flags)
30322{
30323 if ( !hotbarFrame || hotbarSlot < 0 || hotbarSlot >= NUM_HOTBAR_SLOTS )
30324 {
30325 return false;
30326 }
30327 if ( auto slotFrame = getHotbarSlotFrame(hotbarSlot) )
30328 {
30329 slotFrame->warpMouseToFrame(player.playernum, flags);
30330 return true;
30331 }
30332 return false;
30333}
30334
30335Frame* Player::Hotbar_t::getHotbarSlotFrame(const int hotbarSlot)
30336{
30337 if ( !hotbarFrame || hotbarSlot < 0 || hotbarSlot >= NUM_HOTBAR_SLOTS )
30338 {
30339 return nullptr;
30340 }
30341
30342 return hotbarSlotFrames[hotbarSlot];
30343}
30344
30345static void drawConsoleCommandBuffer() {
30346 if (!command) {
30347 return;
30348 }
30349 int commandPlayer = clientnum;
30350 for ( int i = 0; i < MAXPLAYERS4; ++i ) {
30351 if ( inputs.bPlayerUsingKeyboardControl(i) ) {
30352 commandPlayer = i;
30353 break;
30354 }
30355 }
30356 char buf[1024];
30357 if ( (ticks - cursorflash) % TICKS_PER_SECOND50 < TICKS_PER_SECOND50 / 2 ) {
30358 snprintf(buf, sizeof(buf), "> %s_", command_str);
30359 } else {
30360 snprintf(buf, sizeof(buf), "> %s", command_str);
30361 }
30362 const char* font;
30363 if (intro) {
30364 font = "fonts/pixelmix.ttf#16#2";
30365 } else {
30366 if ( players[commandPlayer]->isLocalPlayer() )
30367 {
30368 font = players[commandPlayer]->messageZone.useBigFont ?
30369 "fonts/pixelmix.ttf#16#2" : "fonts/pixel_maz_multiline.ttf#16#2";
30370 }
30371 else
30372 {
30373 font = "fonts/pixelmix.ttf#16#2";
30374 }
30375 }
30376 auto text = Text::get(buf, font, 0xffffffff, makeColor(0, 0, 0, 255));
30377 const int printx = players[commandPlayer]->camera_virtualx1() + 8;
30378 int printy = players[commandPlayer]->camera_virtualy2() - 192;
30379 if ( players[commandPlayer]->messageZone.actualAlignment == Player::MessageZone_t::ALIGN_LEFT_BOTTOM
30380 && players[commandPlayer]->messageZone.chatFrame )
30381 {
30382 if ( Frame* messageBoxFrame = players[commandPlayer]->messageZone.chatFrame->findFrame("message box") )
30383 {
30384 printy = messageBoxFrame->getSize().y + messageBoxFrame->getSize().h + 4;
30385 if ( !players[commandPlayer]->messageZone.useBigFont )
30386 {
30387 printy -= 4;
30388 }
30389 printy += players[commandPlayer]->camera_virtualy1();
30390 }
30391 }
30392 text->draw(SDL_Rect{0,0,0,0}, SDL_Rect{printx, printy, 0, 0},
30393 SDL_Rect{0, 0, Frame::virtualScreenX, Frame::virtualScreenY});
30394}
30395
30396static Uint32 gui_ticks = 0u;
30397Frame::result_t doFrames() {
30398 Frame::result_t result;
30399 result.usable = false;
30400 result.highlightTime = 0;
30401 result.tooltip = nullptr;
30402 result.removed = false;
30403 if ( gui )
30404 {
30405 while ( gui_ticks < ticks )
30406 {
30407 ++gui_ticks;
30408 }
30409
30410 static ConsoleVariable<bool> gui_process("/gui_process", true);
30411 if (*gui_process) {
30412 result = gui->process();
30413 }
30414
30415 static ConsoleVariable<bool> gui_draw("/gui_draw", true);
30416 if (*gui_draw) {
30417 gui->predraw();
30418 gui->draw();
30419 if (!movie) {
30420 drawConsoleCommandBuffer();
30421 }
30422 gui->postdraw();
30423 }
30424 }
30425 return result;
30426}
30427
30428real_t Player::SkillSheet_t::windowCompactHeightScaleX = 0.0;
30429real_t Player::SkillSheet_t::windowCompactHeightScaleY = 0.0;
30430real_t Player::SkillSheet_t::windowHeightScaleX = 0.0;
30431real_t Player::SkillSheet_t::windowHeightScaleY = 0.0;
30432bool Player::SkillSheet_t::generateFollowerTableForSkillsheet = false;
30433SkillSheetFrames_t skillSheetEntryFrames[MAXPLAYERS4];
30434
30435void Player::SkillSheet_t::createSkillSheet()
30436{
30437 if ( skillFrame )
30438 {
30439 return;
30440 }
30441
30442 char name[32];
30443 snprintf(name, sizeof(name), "player skills %d", player.playernum);
30444 Frame* frame = gameUIFrame[player.playernum]->addFrame(name);
30445 skillFrame = frame;
30446 frame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(),
30447 players[player.playernum]->camera_virtualy1(),
30448 players[player.playernum]->camera_virtualWidth(),
30449 players[player.playernum]->camera_virtualHeight() });
30450 frame->setHollow(false);
30451 frame->setBorder(0);
30452 frame->setOwner(player.playernum);
30453 frame->setInheritParentFrameOpacity(false);
30454
30455 auto fade = skillFrame->addImage(
30456 SDL_Rect{ 0, 0, skillFrame->getSize().w, skillFrame->getSize().h },
30457 0, "images/system/white.png", "fade img");
30458 Uint8 r, g, b, a;
30459 getColor(fade->color, &r, &g, &b, &a);
30460 a = 0;
30461 fade->color = makeColor( r, g, b, a);
30462 fade->disabled = true;
30463
30464 Frame* skillBackground = frame->addFrame("skills frame");
30465 skillBackground->setHollow(true);
30466 skillSheetEntryFrames[player.playernum].skillsFrame = skillBackground;
30467 const int width = 684;
30468 const int height = 404;
30469 skillBackground->setInheritParentFrameOpacity(false);
30470 skillBackground->setSize(SDL_Rect{ frame->getSize().x, frame->getSize().y, width, height });
30471 /*auto bgImg = skillBackground->addImage(SDL_Rect{ 0, 0, width, height }, 0xFFFFFFFF,
30472 "*#images/ui/SkillSheet/UI_Skills_Window_02.png", "skills img");*/
30473
30474 Frame* allSkillEntriesLeft = skillBackground->addFrame("skill entries frame left");
30475 allSkillEntriesLeft->setHollow(true);
30476 Frame* allSkillEntriesRight = skillBackground->addFrame("skill entries frame right");
30477 allSkillEntriesRight->setHollow(true);
30478 skillSheetEntryFrames[player.playernum].entryFrameLeft = allSkillEntriesLeft;
30479 skillSheetEntryFrames[player.playernum].entryFrameRight = allSkillEntriesRight;
30480 //allSkillEntries->setSize(SDL_Rect{ 0, 0, skillBackground->getSize().w, skillBackground->getSize().h });
30481
30482 SDL_Rect allSkillEntriesPosLeft{ 0, 0, 182, skillBackground->getSize().h };
30483 SDL_Rect allSkillEntriesPosRight{ skillBackground->getSize().w - 182, 0, 182, skillBackground->getSize().h };
30484 allSkillEntriesLeft->setSize(allSkillEntriesPosLeft);
30485 allSkillEntriesRight->setSize(allSkillEntriesPosRight);
30486
30487 allSkillEntriesLeft->addImage(SDL_Rect{ 0, 12, 182, 376 },
30488 0xFFFFFFFF, "*#images/ui/SkillSheet/UI_Skills_Window_Left_04.png", "bg wing left");
30489 allSkillEntriesLeft->addImage(SDL_Rect{0, 0, 0, 0},
30490 0xFFFFFFFF, "*#images/ui/SkillSheet/UI_Skills_Left_Full_00000000.png", "bg wing all skills left");
30491 allSkillEntriesRight->addImage(SDL_Rect{ 0, 12, 182, 376 },
30492 0xFFFFFFFF, "*#images/ui/SkillSheet/UI_Skills_Window_Right_04.png", "bg wing right");
30493 allSkillEntriesRight->addImage(SDL_Rect{ 0, 0, 0, 0 },
30494 0xFFFFFFFF, "*#images/ui/SkillSheet/UI_Skills_Right_Full_00000000.png", "bg wing all skills right");
30495
30496 auto skillBackgroundImagesFrame = skillBackground->addFrame("skills bg images");
30497 skillBackgroundImagesFrame->setHollow(true);
30498 skillSheetEntryFrames[player.playernum].skillBgImgsFrame = skillBackgroundImagesFrame;
30499 skillBackgroundImagesFrame->setSize(SDL_Rect{ 0, 0, skillBackground->getSize().w, skillBackground->getSize().h });
30500 {
30501 Uint32 color = makeColor(255, 255, 255, 255);
30502 skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30503 color, "*#images/ui/SkillSheet/UI_Skills_Window_TL_04.png", skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
30504 skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30505 color, "*#images/ui/SkillSheet/UI_Skills_Window_TR_04.png", skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
30506 skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30507 color, "*#images/ui/SkillSheet/UI_Skills_Window_T_04.png", skillsheetEffectBackgroundImages[TOP].c_str());
30508 skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30509 color, "*#images/ui/SkillSheet/UI_Skills_Window_L_03.png", skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str());
30510 skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30511 color, "*#images/ui/SkillSheet/UI_Skills_Window_R_03.png", skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str());
30512 skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30513 makeColor(0, 0, 0, 255), "images/system/white.png", skillsheetEffectBackgroundImages[MIDDLE].c_str());
30514 skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30515 color, "*#images/ui/SkillSheet/UI_Skills_Window_BL_03.png", skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str());
30516 skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30517 color, "*#images/ui/SkillSheet/UI_Skills_Window_BR_03.png", skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str());
30518 skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30519 color, "*#images/ui/SkillSheet/UI_Skills_Window_B_03.png", skillsheetEffectBackgroundImages[BOTTOM].c_str());
30520 imageSetWidthHeight9x9(skillBackgroundImagesFrame, skillsheetEffectBackgroundImages);
30521
30522 skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 78, 18 },
30523 color, "*#images/ui/SkillSheet/UI_Skills_Window_Flourish_T.png", "flourish top");
30524 skillBackgroundImagesFrame->addImage(SDL_Rect{ 0, 0, 34, 14 },
30525 color, "*#images/ui/SkillSheet/UI_Skills_Window_Flourish_B.png", "flourish bottom");
30526 }
30527
30528 const int skillEntryStartY = 38;
30529 SDL_Rect skillEntryPos{ 0, skillEntryStartY, 182, 40 };
30530 SDL_Rect skillSelectorPos{ 0, 2, 146, 32 };
30531 const char* boldFont = "fonts/pixel_maz_multiline.ttf#16#2";
30532 const char* numberFont = "fonts/pixelmix.ttf#16";
30533 //const char* titleFont = "fonts/pixel_maz_multiline.ttf#24#2";
30534 const char* titleFont = "fonts/pixelmix.ttf#24#2";
30535 const char* descFont = "fonts/pixel_maz_multiline.ttf#16#2";
30536 for ( int i = 0; i < 8; ++i )
30537 {
30538 char skillname[32];
30539 snprintf(skillname, sizeof(skillname), "skill %d", i);
30540 Frame* entry = allSkillEntriesRight->addFrame(skillname);
30541 entry->setHollow(true);
30542 skillSheetEntryFrames[player.playernum].entryFrames[i] = entry;
30543 entry->setSize(skillEntryPos);
30544 auto selector = entry->addImage(skillSelectorPos, 0xFFFFFFFF, "*#images/ui/SkillSheet/UI_Skills_SkillSelector_00.png", "selector img");
30545 SDL_Rect imgBgPos{ skillEntryPos.w - 36, 0, 36, 36 };
30546 entry->addImage(imgBgPos, 0xFFFFFFFF, "*#images/ui/SkillSheet/UI_Skills_Icons_BG00_00.png", "skill icon bg");
30547 SDL_Rect imgFgPos{ imgBgPos.x + 6, imgBgPos.y + 6, 24, 24 };
30548 entry->addImage(imgFgPos, 0xFFFFFFFF, "", "skill icon fg");
30549 SDL_Rect statPos{ 10, 6, 24, 24 };
30550 auto statIcon = entry->addImage(statPos, 0xFFFFFFFF, "", "stat icon");
30551 if ( i < skillSheetData.skillEntries.size() )
30552 {
30553 statIcon->path = skillSheetData.skillEntries[i].statIconPath;
30554 }
30555 skillEntryPos.y += skillEntryPos.h;
30556
30557 SDL_Rect profNamePos{ statPos.x + statPos.w - 32, 4, 98, 28 };
30558 Field* profName = entry->addField("skill name", 64);
30559 profName->setSize(profNamePos);
30560 profName->setHJustify(Field::justify_t::RIGHT);
30561 profName->setVJustify(Field::justify_t::CENTER);
30562 profName->setFont(boldFont);
30563 profName->setTextColor(skillSheetData.defaultTextColor);
30564 if ( i < skillSheetData.skillEntries.size() )
30565 {
30566 profName->setText(skillSheetData.skillEntries[i].name.c_str());
30567 }
30568 else
30569 {
30570 profName->setText("Default");
30571 }
30572
30573 SDL_Rect profLevelPos{ profNamePos.x + profNamePos.w + 8 - 2, profNamePos.y, 34, 28 };
30574 Field* profLevel = entry->addField("skill level", 16);
30575 profLevel->setSize(profLevelPos);
30576 profLevel->setHJustify(Field::justify_t::RIGHT);
30577 profLevel->setVJustify(Field::justify_t::CENTER);
30578 profLevel->setFont(numberFont);
30579 profLevel->setTextColor(skillSheetData.defaultTextColor);
30580 profLevel->setText("0");
30581 }
30582
30583 skillEntryPos.x = 0;
30584 skillEntryPos.y = skillEntryStartY;
30585 skillSelectorPos.x = 36;
30586
30587 for ( int i = 8; i < 16; ++i )
30588 {
30589 char skillname[32];
30590 snprintf(skillname, sizeof(skillname), "skill %d", i);
30591 Frame* entry = allSkillEntriesLeft->addFrame(skillname);
30592 entry->setHollow(true);
30593 skillSheetEntryFrames[player.playernum].entryFrames[i] = entry;
30594 entry->setSize(skillEntryPos);
30595 auto selector = entry->addImage(skillSelectorPos, 0xFFFFFFFF, "*#images/ui/SkillSheet/UI_Skills_SkillSelectorR_00.png", "selector img");
30596 SDL_Rect imgBgPos{ 0, 0, 36, 36 };
30597 entry->addImage(imgBgPos, 0xFFFFFFFF, "*#images/ui/SkillSheet/UI_Skills_Icons_BG00_00.png", "skill icon bg");
30598 SDL_Rect imgFgPos{ imgBgPos.x + 6, imgBgPos.y + 6, 24, 24 };
30599 entry->addImage(imgFgPos, 0xFFFFFFFF, "*#images/ui/SkillSheet/icons/Alchemy01.png", "skill icon fg");
30600 SDL_Rect statPos{ skillEntryPos.w - 10 - 24, 6, 24, 24 };
30601 auto statIcon = entry->addImage(statPos, 0xFFFFFFFF, "", "stat icon");
30602 if ( i < skillSheetData.skillEntries.size() )
30603 {
30604 statIcon->path = skillSheetData.skillEntries[i].statIconPath;
30605 }
30606 skillEntryPos.y += skillEntryPos.h;
30607
30608 SDL_Rect profNamePos{ statPos.x - 98 + 32, 4, 98, 28 };
30609 Field* profName = entry->addField("skill name", 64);
30610 profName->setSize(profNamePos);
30611 profName->setHJustify(Field::justify_t::LEFT);
30612 profName->setVJustify(Field::justify_t::CENTER);
30613 profName->setFont(boldFont);
30614 profName->setTextColor(skillSheetData.defaultTextColor);
30615 if ( i < skillSheetData.skillEntries.size() )
30616 {
30617 profName->setText(skillSheetData.skillEntries[i].name.c_str());
30618 }
30619 else
30620 {
30621 profName->setText("Default");
30622 }
30623
30624 SDL_Rect profLevelPos{ profNamePos.x - 12 - 32, profNamePos.y, 34, 28 };
30625 Field* profLevel = entry->addField("skill level", 16);
30626 profLevel->setSize(profLevelPos);
30627 profLevel->setHJustify(Field::justify_t::RIGHT);
30628 profLevel->setVJustify(Field::justify_t::CENTER);
30629 profLevel->setFont(numberFont);
30630 profLevel->setTextColor(skillSheetData.defaultTextColor);
30631 profLevel->setText("0");
30632 }
30633
30634 SDL_Rect skillTitlePos{ skillBackground->getSize().w / 2 - 320 / 2, 14, 320, 40 };
30635 auto skillTitleTxt = skillBackground->addField("skill title txt", 64);
30636 skillTitleTxt->setHJustify(Field::justify_t::CENTER);
30637 skillTitleTxt->setVJustify(Field::justify_t::CENTER);
30638 skillTitleTxt->setText("");
30639 skillTitleTxt->setSize(skillTitlePos);
30640 skillTitleTxt->setFont(titleFont);
30641 skillTitleTxt->setOntop(true);
30642 skillTitleTxt->setTextColor(makeColor(201, 162, 100, 255));
30643
30644 {
30645 auto closeBtn = skillBackground->addButton("close skills button");
30646 SDL_Rect closeBtnPos;
30647 closeBtnPos.x = skillBackground->getSize().w - 66;
30648 closeBtnPos.y = 22;
30649 closeBtnPos.w = 26;
30650 closeBtnPos.h = 26;
30651 closeBtn->setSize(closeBtnPos);
30652 closeBtn->setColor(makeColor(255, 255, 255, 255));
30653 closeBtn->setHighlightColor(makeColor(255, 255, 255, 255));
30654 closeBtn->setTextHighlightColor(makeColor(201, 162, 100, 255));
30655 closeBtn->setText("X");
30656 closeBtn->setOntop(true);
30657 closeBtn->setFont(boldFont);
30658 closeBtn->setHideGlyphs(true);
30659 closeBtn->setHideKeyboardGlyphs(true);
30660 closeBtn->setHideSelectors(true);
30661 closeBtn->setMenuConfirmControlType(0);
30662 closeBtn->setBackground("*#images/ui/Skillsheet/Button_X_00.png");
30663 closeBtn->setBackgroundHighlighted("*#images/ui/Skillsheet/Button_XHigh_00.png");
30664 closeBtn->setBackgroundActivated("*#images/ui/Skillsheet/Button_XPress_00.png");
30665 closeBtn->setCallback([](Button& button) {
30666 players[button.getOwner()]->skillSheet.closeSkillSheet();
30667 Player::soundCancel();
30668 });
30669 closeBtn->setTickCallback([](Widget& widget) {
30670 if ( widget.isSelected() )
30671 {
30672 if ( !inputs.getVirtualMouse(widget.getOwner())->draw_cursor )
30673 {
30674 widget.deselect();
30675 }
30676 }
30677 });
30678 }
30679
30680 SDL_Rect descPos{ 0, 54, 320, 324 };
30681 descPos.x = skillBackground->getSize().w / 2 - descPos.w / 2;
30682 auto skillDescriptionFrame = skillBackground->addFrame("skill desc frame");
30683 skillDescriptionFrame->setHollow(true);
30684 skillSheetEntryFrames[player.playernum].skillDescFrame = skillDescriptionFrame;
30685 skillDescriptionFrame->setSize(descPos);
30686
30687 /*auto debugRect = skillDescriptionFrame->addImage(SDL_Rect{ 0, 0, descPos.w, descPos.h }, 0xFFFFFFFF,
30688 "images/system/white.png", "")*/;
30689
30690 auto slider = skillDescriptionFrame->addSlider("skill slider");
30691 slider->setBorder(24);
30692 slider->setMinValue(0);
30693 slider->setMaxValue(100);
30694 slider->setValue(0);
30695 SDL_Rect sliderPos{ descPos.w - 34, 4, 30, descPos.h - 8 };
30696 slider->setRailSize(sliderPos);
30697 slider->setHandleSize(SDL_Rect{ 0, 0, 34, 34 });
30698 slider->setOrientation(Slider::SLIDER_VERTICAL);
30699 //slider->setCallback(callback);
30700 slider->setColor(makeColor(255, 255, 255, 255));
30701 slider->setHighlightColor(makeColor(255, 255, 255, 255));
30702 slider->setHandleImage("*#images/ui/Main Menus/Settings/Settings_Slider_Boulder00.png");
30703 slider->setRailImage("*#images/ui/Main Menus/Settings/Settings_Slider_Backing00.png");
30704 slider->setHideGlyphs(true);
30705 slider->setHideKeyboardGlyphs(true);
30706 slider->setHideSelectors(true);
30707 slider->setMenuConfirmControlType(0);
30708
30709 Font* actualFont = Font::get(descFont);
30710 int fontHeight;
30711 actualFont->sizeText("_", nullptr, &fontHeight);
30712
30713 auto scrollAreaOuterFrame = skillDescriptionFrame->addFrame("scroll area outer frame");
30714 scrollAreaOuterFrame->setHollow(true);
30715 skillSheetEntryFrames[player.playernum].scrollAreaOuterFrame = scrollAreaOuterFrame;
30716 scrollAreaOuterFrame->setSize(SDL_Rect{ 16, 4, sliderPos.x - 4 - 16, descPos.h - 8 });
30717 auto scrollAreaFrame = scrollAreaOuterFrame->addFrame("skill scroll area");
30718 scrollAreaFrame->setHollow(true);
30719 skillSheetEntryFrames[player.playernum].scrollArea = scrollAreaFrame;
30720 scrollAreaFrame->setSize(SDL_Rect{ 0, 0, scrollAreaOuterFrame->getSize().w, 1000 });
30721
30722 SDL_Rect txtPos{ 0, 0, scrollAreaFrame->getSize().w, scrollAreaFrame->getSize().h };
30723 {
30724 auto skillLvlHeaderTxt = scrollAreaFrame->addField("skill lvl header txt", 128);
30725 skillLvlHeaderTxt->setFont(descFont);
30726 skillLvlHeaderTxt->setSize(txtPos);
30727 skillLvlHeaderTxt->setText(Language::get(5961));
30728
30729 auto skillLvlHeaderVal = scrollAreaFrame->addField("skill lvl header val", 128);
30730 skillLvlHeaderVal->setFont(descFont);
30731 skillLvlHeaderVal->setSize(txtPos);
30732 skillLvlHeaderVal->setText("");
30733 skillLvlHeaderVal->setHJustify(Field::justify_t::RIGHT);
30734
30735 txtPos.y += fontHeight;
30736 }
30737 {
30738 const int iconSize = 24;
30739 auto statIcon = scrollAreaFrame->addImage(
30740 SDL_Rect{ txtPos.x + txtPos.w - iconSize, txtPos.y, iconSize, iconSize },
30741 0xFFFFFFFF, "*#images/ui/CharSheet/HUD_CharSheet_DEX_00.png", "stat icon");
30742
30743 txtPos.y += std::max(0, iconSize - fontHeight);
30744 auto statHeaderTxt = scrollAreaFrame->addField("stat header txt", 128);
30745 statHeaderTxt->setFont(descFont);
30746 statHeaderTxt->setSize(txtPos);
30747 statHeaderTxt->setText(Language::get(5962));
30748
30749 auto statTypeTxt = scrollAreaFrame->addField("stat type txt", 128);
30750 statTypeTxt->setFont(descFont);
30751 txtPos.w -= iconSize + 4;
30752 statTypeTxt->setSize(txtPos);
30753 txtPos.w += iconSize + 4;
30754 statTypeTxt->setText("");
30755 statTypeTxt->setHJustify(Field::justify_t::RIGHT);
30756 txtPos.y += fontHeight;
30757 txtPos.y += fontHeight / 2;
30758 }
30759 {
30760 auto skillDescriptionTxt = scrollAreaFrame->addField("skill desc txt", 1024);
30761 skillDescriptionTxt->setFont(descFont);
30762 skillDescriptionTxt->setSize(txtPos);
30763 skillDescriptionTxt->setText("");
30764 //skillDescriptionTxt->setTextColor(makeColor(201, 162, 100, 255));
30765 skillDescriptionTxt->setHJustify(Field::justify_t::CENTER);
30766 skillDescriptionTxt->setOntop(true);
30767
30768 auto skillDescriptionBgFrame = scrollAreaFrame->addFrame("skill desc bg frame");
30769 skillDescriptionBgFrame->setHollow(true);
30770 {
30771 //Uint32 color = makeColor(22, 24, 29, 255);
30772 Uint32 color = makeColor(255, 255, 255, 128);
30773 skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30774 color, "*#images/ui/SkillSheet/UI_Skills_LegendBox_TL_00.png", skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
30775 skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30776 color, "*#images/ui/SkillSheet/UI_Skills_LegendBox_TR_00.png", skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
30777 skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30778 color, "*#images/ui/SkillSheet/UI_Skills_LegendBox_T_00.png", skillsheetEffectBackgroundImages[TOP].c_str());
30779 skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30780 color, "*#images/ui/SkillSheet/UI_Skills_LegendBox_ML_00.png", skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str());
30781 skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30782 color, "*#images/ui/SkillSheet/UI_Skills_LegendBox_MR_00.png", skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str());
30783 skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30784 color, "*#images/ui/SkillSheet/UI_Skills_LegendBox_M_00.png", skillsheetEffectBackgroundImages[MIDDLE].c_str());
30785 skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30786 color, "*#images/ui/SkillSheet/UI_Skills_LegendBox_BL_00.png", skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str());
30787 skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30788 color, "*#images/ui/SkillSheet/UI_Skills_LegendBox_BR_00.png", skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str());
30789 skillDescriptionBgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30790 color, "*#images/ui/SkillSheet/UI_Skills_LegendBox_B_00.png", skillsheetEffectBackgroundImages[BOTTOM].c_str());
30791 imageSetWidthHeight9x9(skillDescriptionBgFrame, skillsheetEffectBackgroundImages);
30792 }
30793
30794
30795 int txtHeight = skillDescriptionTxt->getNumTextLines() * actualFont->height(true);
30796
30797 txtPos.y += txtHeight;
30798 txtPos.y += fontHeight / 2;
30799
30800 }
30801 {
30802 char effectFrameName[64] = "";
30803 char effectFieldName[64] = "";
30804 char effectFieldVal[64] = "";
30805 char effectBgName[64];
30806 int effectXOffset = 72; // tmp paramters - configured in skillsheet json
30807 int effectBackgroundXOffset = 8;
30808 int effectBackgroundWidth = 80;
30809 auto effectLargeBgImg = scrollAreaFrame->addImage(
30810 SDL_Rect{ 0, 0, 0, 0 },
30811 makeColor(10, 4, 4, 255), "images/system/white.png", "effect frame bg tmp");
30812 effectLargeBgImg->disabled = true;
30813 for ( int i = 0; i < 10; ++i )
30814 {
30815 snprintf(effectFrameName, sizeof(effectFrameName), "effect %d frame", i);
30816 auto effectFrame = scrollAreaFrame->addFrame(effectFrameName);
30817 effectFrame->setHollow(true);
30818 skillSheetEntryFrames[player.playernum].effectFrames[i] = effectFrame;
30819 effectFrame->setSize(SDL_Rect{ txtPos.x, txtPos.y - 2, txtPos.w, fontHeight + 8 });
30820 effectFrame->addImage(
30821 SDL_Rect{ 0, 0, effectFrame->getSize().w, effectFrame->getSize().h - 4 },
30822 makeColor(91, 73, 57, 255), "images/system/white.png", "effect frame bg highlight");
30823 int valueX = effectFrame->getSize().w - effectXOffset;
30824
30825 auto valBgImgFrame = effectFrame->addFrame("effect val bg frame");
30826 valBgImgFrame->setHollow(true);
30827 valBgImgFrame->setSize(SDL_Rect{ valueX - effectBackgroundXOffset, 0, effectBackgroundWidth, effectFrame->getSize().h - 4});
30828 {
30829 Uint32 color = makeColor(51, 33, 26, 255);
30830 valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30831 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_TL00.png", skillsheetEffectBackgroundImages[TOP_LEFT].c_str());
30832 valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30833 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_TR00.png", skillsheetEffectBackgroundImages[TOP_RIGHT].c_str());
30834 valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30835 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_T00.png", skillsheetEffectBackgroundImages[TOP].c_str());
30836 valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30837 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_L00.png", skillsheetEffectBackgroundImages[MIDDLE_LEFT].c_str());
30838 valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30839 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_R00.png", skillsheetEffectBackgroundImages[MIDDLE_RIGHT].c_str());
30840 valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30841 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_M00.png", skillsheetEffectBackgroundImages[MIDDLE].c_str());
30842 valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30843 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_BL00.png", skillsheetEffectBackgroundImages[BOTTOM_LEFT].c_str());
30844 valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30845 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_BR00.png", skillsheetEffectBackgroundImages[BOTTOM_RIGHT].c_str());
30846 valBgImgFrame->addImage(SDL_Rect{ 0, 0, 6, 6 },
30847 color, "*#images/ui/SkillSheet/UI_Skills_EffectBG_B00.png", skillsheetEffectBackgroundImages[BOTTOM].c_str());
30848
30849 /*valBgImgFrame->addImage(
30850 SDL_Rect{ 0, 0, effectFrame->getSize().w, effectFrame->getSize().h - 4 },
30851 makeColor(255, 255, 255, 128), "images/system/white.png", "tmp tmp");*/
30852 imageSetWidthHeight9x9(valBgImgFrame, skillsheetEffectBackgroundImages);
30853 }
30854
30855 auto effectTxtFrame = effectFrame->addFrame("effect txt frame");
30856 effectTxtFrame->setHollow(true);
30857 effectTxtFrame->setSize(SDL_Rect{ 0, 0, effectFrame->getSize().w - effectXOffset - effectBackgroundXOffset, effectFrame->getSize().h - 4 });
30858 auto effectTxt = effectTxtFrame->addField("effect txt", 1024);
30859 effectTxt->setFont(descFont);
30860 effectTxt->setSize(SDL_Rect{0, 0, 1000, effectTxtFrame->getSize().h}); // large 1000px to handle large text length marquee
30861 effectTxt->setVJustify(Field::justify_t::CENTER);
30862 effectTxt->setText("");
30863 effectTxt->setTextColor(makeColor(201, 162, 100, 255));
30864
30865 auto effectValFrame = effectFrame->addFrame("effect val frame");
30866 effectValFrame->setHollow(true);
30867 effectValFrame->setSize(SDL_Rect{ valueX, 0, effectXOffset, effectFrame->getSize().h - 4 });
30868 auto effectVal = effectValFrame->addField("effect val", 1024);
30869 effectVal->setFont(descFont);
30870 effectVal->setSize(SDL_Rect{ 0, 0, 1000, effectValFrame->getSize().h }); // large 1000px to handle large text length marquee
30871 effectVal->setVJustify(Field::justify_t::CENTER);
30872 effectVal->setText("");
30873 effectVal->setTextColor(makeColor(201, 162, 100, 255));
30874
30875 //effectFrame->setDisabled(true);
30876
30877 txtPos.y += effectFrame->getSize().h;
30878 }
30879 }
30880 {
30881 auto legendDivImg = scrollAreaFrame->addImage(SDL_Rect{ 0, txtPos.y, 212, 28 }, 0xFFFFFFFF,
30882 "*#images/ui/SkillSheet/UI_Skills_Separator_00.png", "legend div");
30883 legendDivImg->pos.x = scrollAreaFrame->getSize().w / 2 - legendDivImg->pos.w / 2;
30884 auto legendDivTxt = scrollAreaFrame->addField("legend div text", 128);
30885 legendDivTxt->setText(Language::get(4056));
30886 SDL_Rect legendDivTxtPos = legendDivImg->pos;
30887 legendDivTxt->setSize(legendDivTxtPos);
30888 legendDivTxt->setFont(descFont);
30889 legendDivTxt->setHJustify(Field::justify_t::CENTER);
30890
30891 auto legendFrame = scrollAreaFrame->addFrame("legend frame");
30892 legendFrame->setHollow(true);
30893 skillSheetEntryFrames[player.playernum].legendFrame = legendFrame;
30894 SDL_Rect legendPos{ 0, txtPos.y, txtPos.w, 100 };
30895 legendFrame->setSize(legendPos);
30896 auto tl = legendFrame->addImage(SDL_Rect{ 0, 0, 18, 18 }, 0xFFFFFFFF,
30897 "*#images/ui/SkillSheet/UI_Skills_LegendBox_TL_00.png", "top left img");
30898 auto tm = legendFrame->addImage(SDL_Rect{ 18, 0, legendPos.w - 18 * 2, 18 }, 0xFFFFFFFF,
30899 "*#images/ui/SkillSheet/UI_Skills_LegendBox_T_00.png", "top img");
30900 auto tr = legendFrame->addImage(SDL_Rect{ legendPos.w - 18, 0, 18, 18 }, 0xFFFFFFFF,
30901 "*#images/ui/SkillSheet/UI_Skills_LegendBox_TR_00.png", "top right img");
30902
30903 auto ml = legendFrame->addImage(SDL_Rect{ 0, 18, 18, legendPos.h - 18 * 2 }, 0xFFFFFFFF,
30904 "*#images/ui/SkillSheet/UI_Skills_LegendBox_ML_00.png", "middle left img");
30905 auto mm = legendFrame->addImage(SDL_Rect{ 18, 18, legendPos.w - 18 * 2, ml->pos.h }, 0xFFFFFFFF,
30906 "*#images/ui/SkillSheet/UI_Skills_LegendBox_M_00.png", "middle img");
30907 auto mr = legendFrame->addImage(SDL_Rect{ legendPos.w - 18, 18, 18, ml->pos.h }, 0xFFFFFFFF,
30908 "*#images/ui/SkillSheet/UI_Skills_LegendBox_MR_00.png", "middle right img");
30909
30910 auto bl = legendFrame->addImage(SDL_Rect{ 0, ml->pos.y + ml->pos.h, 18, 18 }, 0xFFFFFFFF,
30911 "*#images/ui/SkillSheet/UI_Skills_LegendBox_BL_00.png", "bottom left img");
30912 auto bm = legendFrame->addImage(SDL_Rect{ 18, bl->pos.y, legendPos.w - 18 * 2, 18 }, 0xFFFFFFFF,
30913 "*#images/ui/SkillSheet/UI_Skills_LegendBox_B_00.png", "bottom img");
30914 auto br = legendFrame->addImage(SDL_Rect{ legendPos.w - 18, bl->pos.y, 18, 18 }, 0xFFFFFFFF,
30915 "*#images/ui/SkillSheet/UI_Skills_LegendBox_BR_00.png", "bottom right img");
30916
30917 auto backerImg = scrollAreaFrame->addImage(SDL_Rect{ 0, 0, 154, 12 }, 0xFFFFFFFF,
30918 "*#images/ui/SkillSheet/UI_Skills_LegendTextBacker_00.png", "legend txt backer img");
30919 backerImg->disabled = true;
30920
30921 auto legendText = legendFrame->addField("legend text", 256);
30922 legendText->setText("");
30923 legendText->setSize(mm->pos);
30924 legendText->setFont(descFont);
30925 legendText->setHJustify(Field::justify_t::CENTER);
30926
30927 skillBackground->setBlitChildren(true);
30928 }
30929
30930 std::string promptFont = "fonts/pixel_maz.ttf#32#2";
30931 const int promptWidth = 60;
30932 const int promptHeight = 27;
30933 auto promptBack = frame->addField("prompt back txt", 16);
30934 promptBack->setSize(SDL_Rect{ frame->getSize().w - promptWidth - 16, // lower right corner
30935 0, promptWidth, promptHeight });
30936 promptBack->setFont(promptFont.c_str());
30937 promptBack->setHJustify(Field::justify_t::RIGHT);
30938 promptBack->setVJustify(Field::justify_t::CENTER);
30939 promptBack->setText(Language::get(4053));
30940 //promptBack->setOntop(true);
30941 promptBack->setTextColor(makeColor(201, 162, 100, 255));
30942
30943 auto promptBackImg = frame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
30944 "", "prompt back img");
30945 promptBackImg->disabled = true;
30946 //promptBackImg->ontop = true;
30947
30948 auto promptScroll = frame->addField("prompt scroll txt", 16);
30949 promptScroll->setSize(SDL_Rect{ frame->getSize().w - promptWidth - 16, // lower right corner
30950 0, promptWidth, promptHeight });
30951 promptScroll->setFont(promptFont.c_str());
30952 promptScroll->setHJustify(Field::justify_t::LEFT);
30953 promptScroll->setVJustify(Field::justify_t::CENTER);
30954 promptScroll->setText(Language::get(4062));
30955 //promptScroll->setOntop(true);
30956 promptScroll->setTextColor(makeColor(201, 162, 100, 255));
30957
30958 auto promptScrollImg = frame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
30959 "", "prompt scroll img");
30960 promptScrollImg->disabled = true;
30961 promptScrollImg->ontop = true;
30962}
30963
30964struct SkillSheetCache_t
30965{
30966 int proficiencyLevelCached;
30967 int highlightedSkill = -2;
30968 int selectedSkill = -2;
30969};
30970SkillSheetCache_t skillsheetCache[MAXPLAYERS4][NUMPROFICIENCIES];
30971
30972void Player::SkillSheet_t::resetSkillDisplay()
30973{
30974 bSkillSheetEntryLoaded = false;
30975 scrollInertia = 0.0;
30976 for ( int i = 0; i < NUMPROFICIENCIES; ++i )
30977 {
30978 skillsheetCache[player.playernum][i].proficiencyLevelCached = -1;
30979 skillsheetCache[player.playernum][i].highlightedSkill = -2;
30980 skillsheetCache[player.playernum][i].selectedSkill = -2;
30981 }
30982
30983 for ( auto& skillEntry : skillSheetData.skillEntries )
30984 {
30985 for ( auto& skillEffect : skillEntry.effects )
30986 {
30987 skillEffect.marqueeTicks[player.playernum] = 0;
30988 skillEffect.marquee[player.playernum] = 0.0;
30989 skillEffect.marqueeCompleted[player.playernum] = false;
30990 skillEffect.effectUpdatedAtSkillLevel = -1;
30991 skillEffect.cachedWidth = -1;
30992 skillEffect.effectUpdatedAtMonsterType = -1;
30993 }
30994 }
30995}
30996
30997void Player::SkillSheet_t::closeSkillSheet()
30998{
30999 bSkillSheetOpen = false;
31000 if ( skillFrame )
31001 {
31002 skillFrame->setDisabled(true);
31003 }
31004 resetSkillDisplay();
31005
31006 if ( player.gui_mode == GUI_MODE_NONE )
31007 {
31008 player.shootmode = true;
31009 }
31010 else
31011 {
31012 player.GUI.returnToPreviousActiveModule();
31013 }
31014}
31015
31016static ConsoleVariable<bool> cvar_skillsheet_blit("/skillsheet_blit", true);
31017void Player::SkillSheet_t::openSkillSheet()
31018{
31019 player.GUI.previousModule = player.GUI.activeModule;
31020 if ( player.shootmode )
31021 {
31022 players[player.playernum]->openStatusScreen(GUI_MODE_NONE,
31023 INVENTORY_MODE_ITEM, player.GUI.MODULE_SKILLS_LIST);
31024 }
31025 else
31026 {
31027 players[player.playernum]->openStatusScreen(GUI_MODE_INVENTORY,
31028 players[player.playernum]->inventory_mode, player.GUI.MODULE_SKILLS_LIST); // Reset the GUI to the inventory.
31029 }
31030 if ( !bSkillSheetOpen )
31031 {
31032 Player::soundActivate();
31033 }
31034 bSkillSheetOpen = true;
31035 openTick = ticks;
31036 scrollPercent = 0.0;
31037 if ( skillFrame )
31038 {
31039 auto innerFrame = skillFrame->findFrame("skills frame");
31040 innerFrame->setBlitChildren(*cvar_skillsheet_blit);
31041 auto skillDescriptionFrame = innerFrame->findFrame("skill desc frame");
31042 auto slider = skillDescriptionFrame->findSlider("skill slider");
31043 slider->setValue(0.0);
31044 }
31045 resetSkillDisplay();
31046 if ( selectedSkill < 0 )
31047 {
31048 selectSkill(0);
31049 }
31050 if ( !::inputs.getVirtualMouse(player.playernum)->draw_cursor )
31051 {
31052 highlightedSkill = selectedSkill;
31053 }
31054 if ( bUseCompactSkillsView || bSlideWindowsOnly )
31055 {
31056 if ( selectedSkill >= 8 )
31057 {
31058 skillSlideAmount = 1.0;
31059 skillSlideDirection = 1;
31060 }
31061 else
31062 {
31063 skillSlideAmount = -1.0;
31064 skillSlideDirection = -1;
31065 }
31066 }
31067}
31068
31069std::string formatSkillSheetEffects(int playernum, int proficiency, std::string& tag, std::string& rawValue)
31070{
31071 char buf[1024] = "";
31072 if ( !players[playernum] ) { return ""; }
31073 Entity* player = players[playernum]->entity;
31074 real_t val = 0.0;
31075 real_t val2 = 0.0;
31076 if ( proficiency == PRO_STEALTH )
31077 {
31078 if ( tag == "STEALTH_VIS_REDUCTION" )
31079 {
31080 //val = stats[playernum]->getModifiedProficiency(proficiency) * 2 * 100 / 512.f; // % visibility reduction
31081 val = 100.f * (255 - TOUCHRANGE32) * (1.0 * (stats[playernum]->getModifiedProficiency(PRO_STEALTH) / 100.f)) / 255.f;
31082 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31083 }
31084 else if ( tag == "STEALTH_SNEAK_VIS" )
31085 {
31086 val = (2 + (stats[playernum]->getModifiedProficiency(proficiency) / 40)); // night vision when sneaking
31087 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31088 }
31089 else if ( tag == "STEALTH_BACKSTAB" )
31090 {
31091 val = (stats[playernum]->getModifiedProficiency(proficiency) / 20 + 2) * 2; // backstab dmg
31092 if ( skillCapstoneUnlocked(playernum, proficiency) )
31093 {
31094 val *= 2;
31095 }
31096 if ( stats[playernum]->helmet && stats[playernum]->helmet->type == HAT_HOOD_ASSASSIN )
31097 {
31098 if ( stats[playernum]->helmet->beatitude >= 0 || shouldInvertEquipmentBeatitude(stats[playernum]) )
31099 {
31100 val += std::min(4 + (2 * abs(stats[playernum]->helmet->beatitude)), 8);
31101 }
31102 else
31103 {
31104 val /= 2;
31105 }
31106 }
31107 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31108 }
31109 else if ( tag == "STEALTH_CURRENT_VIS" )
31110 {
31111 if ( player )
31112 {
31113 val = player->entityLightAfterReductions(*stats[playernum], nullptr);
31114 val = std::max(1, (static_cast<int>(val / 16.0))); // general visibility
31115 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31116 }
31117 else
31118 {
31119 return "-";
31120 }
31121 }
31122 return buf;
31123 }
31124 else if ( proficiency == PRO_RANGED )
31125 {
31126 if ( tag == "RANGED_DMG_RANGE" )
31127 {
31128 if ( proficiency == PRO_POLEARM )
31129 {
31130 val = 100 - (100 - stats[playernum]->getModifiedProficiency(proficiency)) / 3.f; // lowest damage roll
31131 }
31132 else
31133 {
31134 val = 100 - (100 - stats[playernum]->getModifiedProficiency(proficiency)) / 2.f; // lowest damage roll
31135 }
31136 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31137 }
31138 else if ( tag == "RANGED_DMG_EFFECTIVENESS" )
31139 {
31140 if ( proficiency == PRO_POLEARM )
31141 {
31142 val = -25 + (stats[playernum]->getModifiedProficiency(proficiency) / 2); // -25% to +25%
31143 }
31144 else
31145 {
31146 val = -25 + (stats[playernum]->getModifiedProficiency(proficiency) / 2); // -25% to +25%
31147 }
31148 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31149 }
31150 else if ( tag == "RANGED_DEGRADE_CHANCE" )
31151 {
31152 val = 50 + static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20) * 10;
31153 if ( stats[playernum]->type == GOBLIN )
31154 {
31155 val += 20;
31156 if ( stats[playernum]->getModifiedProficiency(proficiency) < SKILL_LEVEL_LEGENDARY )
31157 {
31158 val = std::min(val, 90.0);
31159 }
31160 }
31161 if ( skillCapstoneUnlocked(playernum, proficiency) )
31162 {
31163 val = 0.0;
31164 }
31165 if ( val > 0.0001 )
31166 {
31167 val = 100 / val;
31168 }
31169 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31170 }
31171 else if ( tag == "RANGED_THROWN_DMG" )
31172 {
31173 int skillLVL = stats[playernum]->getModifiedProficiency(proficiency) / 20; // thrown dmg bonus
31174 // +0% baseline
31175 val = 100 * (thrownDamageSkillMultipliers[std::min(skillLVL, 5)] - thrownDamageSkillMultipliers[0]);
31176 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31177 }
31178 else if ( tag == "RANGED_PIERCE_CHANCE" )
31179 {
31180 val = std::min(std::max(statGetPER(stats[playernum], player) / 2, 0), 50);
31181 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31182 }
31183 return buf;
31184 }
31185 else if ( proficiency == PRO_SHIELD )
31186 {
31187 if ( tag == "BLOCK_AC_INCREASE" )
31188 {
31189 val = stats[playernum]->getActiveShieldBonus(false);
31190 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31191 }
31192 else if ( tag == "PASSIVE_AC_INCREASE" )
31193 {
31194 val = stats[playernum]->getPassiveShieldBonus(false);
31195 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31196 }
31197 else if ( tag == "BLOCK_DEGRADE_NORMAL_CHANCE" )
31198 {
31199 val = 25 + (stats[playernum]->type == GOBLIN ? 10 : 0); // degrade > 0 dmg taken
31200 val += 2 * (static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 10));
31201 if ( skillCapstoneUnlocked(playernum, proficiency) )
31202 {
31203 val = 0.0;
31204 }
31205 if ( val > 0.0001 )
31206 {
31207 val = 100 / val;
31208 }
31209 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31210 }
31211 else if ( tag == "BLOCK_DEGRADE_DEFENDING_CHANCE" )
31212 {
31213 val = 10 + (stats[playernum]->type == GOBLIN ? 10 : 0); // degrade on 0 dmg
31214 if ( svFlags & SV_FLAG_HARDCORE )
31215 {
31216 val = 40 + (stats[playernum]->type == GOBLIN ? 10 : 0);
31217 }
31218 val += 2 * (static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 10));
31219 if ( skillCapstoneUnlocked(playernum, proficiency) )
31220 {
31221 val = 0.0;
31222 }
31223 if ( val > 0.0001 )
31224 {
31225 val = 100 / val;
31226 }
31227 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31228 }
31229 return buf;
31230 }
31231 else if ( proficiency == PRO_UNARMED )
31232 {
31233 if ( tag == "UNARMED_DMG_RANGE" )
31234 {
31235 if ( proficiency == PRO_POLEARM )
31236 {
31237 val = 100 - (100 - stats[playernum]->getModifiedProficiency(proficiency)) / 3.f; // lowest damage roll
31238 }
31239 else
31240 {
31241 val = 100 - (100 - stats[playernum]->getModifiedProficiency(proficiency)) / 2.f; // lowest damage roll
31242 }
31243 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31244 }
31245 else if ( tag == "UNARMED_DMG_EFFECTIVENESS" )
31246 {
31247 if ( proficiency == PRO_POLEARM )
31248 {
31249 val = -25 + (stats[playernum]->getModifiedProficiency(proficiency) / 2); // -25% to +25%
31250 }
31251 else
31252 {
31253 val = -25 + (stats[playernum]->getModifiedProficiency(proficiency) / 2); // -25% to +25%
31254 }
31255 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31256 }
31257 else if ( tag == "UNARMED_BONUS_DMG" )
31258 {
31259 val = static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20);
31260 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31261 }
31262 else if ( tag == "GLOVE_DEGRADE_CHANCE" )
31263 {
31264 val = 100 + (stats[playernum]->type == GOBLIN ? 20 : 0); // chance to degrade on > 0 dmg
31265 val += (static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20)) * 10;
31266 if ( svFlags & SV_FLAG_HARDCORE )
31267 {
31268 val *= 2;
31269 }
31270 if ( skillCapstoneUnlocked(playernum, proficiency) )
31271 {
31272 val = 0.0;
31273 }
31274 if ( val > 0.0001 )
31275 {
31276 val = 100 / val;
31277 }
31278 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31279 }
31280 else if ( tag == "GLOVE_DEGRADE0_CHANCE" )
31281 {
31282 val = 8 + (stats[playernum]->type == GOBLIN ? 4 : 0); // chance to degrade on 0 dmg
31283 val += static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20);
31284 if ( svFlags & SV_FLAG_HARDCORE )
31285 {
31286 val *= 2;
31287 }
31288 if ( skillCapstoneUnlocked(playernum, proficiency) )
31289 {
31290 val = 0.0;
31291 }
31292 if ( val > 0.0001 )
31293 {
31294 val = 100 / val;
31295 }
31296 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31297 }
31298 else if ( tag == "UNARMED_KNOCKBACK_DIST" )
31299 {
31300 val = static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20) * 20;
31301 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31302 }
31303 return buf;
31304 }
31305 else if ( proficiency == PRO_SWORD )
31306 {
31307 if ( tag == "SWORD_DMG_RANGE" )
31308 {
31309 if ( proficiency == PRO_POLEARM )
31310 {
31311 val = 100 - (100 - stats[playernum]->getModifiedProficiency(proficiency)) / 3.f; // lowest damage roll
31312 }
31313 else
31314 {
31315 val = 100 - (100 - stats[playernum]->getModifiedProficiency(proficiency)) / 2.f; // lowest damage roll
31316 }
31317 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31318 }
31319 else if ( tag == "SWORD_DMG_EFFECTIVENESS" )
31320 {
31321 if ( proficiency == PRO_POLEARM )
31322 {
31323 val = -25 + (stats[playernum]->getModifiedProficiency(proficiency) / 2); // -25% to +25%
31324 }
31325 else
31326 {
31327 val = -25 + (stats[playernum]->getModifiedProficiency(proficiency) / 2); // -25% to +25%
31328 }
31329 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31330 }
31331 else if ( tag == "SWORD_DEGRADE_CHANCE" )
31332 {
31333 val = 50 + (stats[playernum]->type == GOBLIN ? 20 : 0); // chance to degrade on > 0 dmg
31334 val += (static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20)) * 10;
31335 if ( svFlags & SV_FLAG_HARDCORE )
31336 {
31337 val *= 2;
31338 }
31339 if ( skillCapstoneUnlocked(playernum, proficiency) )
31340 {
31341 val = 0.0;
31342 }
31343 if ( val > 0.0001 )
31344 {
31345 val = 100 / val;
31346 }
31347 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31348 }
31349 else if ( tag == "SWORD_DEGRADE0_CHANCE" )
31350 {
31351 val = 4 + (stats[playernum]->type == GOBLIN ? 4 : 0); // chance to degrade on 0 dmg
31352 val += static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20);
31353 if ( svFlags & SV_FLAG_HARDCORE )
31354 {
31355 val *= 2;
31356 }
31357 if ( skillCapstoneUnlocked(playernum, proficiency) )
31358 {
31359 val = 0.0;
31360 }
31361 if ( val > 0.0001 )
31362 {
31363 val = 100 / val;
31364 }
31365 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31366 }
31367 return buf;
31368 }
31369 else if ( proficiency == PRO_POLEARM )
31370 {
31371 if ( tag == "POLEARM_DMG_RANGE" )
31372 {
31373 if ( proficiency == PRO_POLEARM )
31374 {
31375 val = 100 - (100 - stats[playernum]->getModifiedProficiency(proficiency)) / 3.f; // lowest damage roll
31376 }
31377 else
31378 {
31379 val = 100 - (100 - stats[playernum]->getModifiedProficiency(proficiency)) / 2.f; // lowest damage roll
31380 }
31381 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31382 }
31383 else if ( tag == "POLEARM_DMG_EFFECTIVENESS" )
31384 {
31385 if ( proficiency == PRO_POLEARM )
31386 {
31387 val = -25 + (stats[playernum]->getModifiedProficiency(proficiency) / 2); // -25% to +25%
31388 }
31389 else
31390 {
31391 val = -25 + (stats[playernum]->getModifiedProficiency(proficiency) / 2); // -25% to +25%
31392 }
31393 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31394 }
31395 else if ( tag == "POLEARM_DEGRADE_CHANCE" )
31396 {
31397 val = 50 + (stats[playernum]->type == GOBLIN ? 20 : 0); // chance to degrade on > 0 dmg
31398 val += (static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20)) * 10;
31399 if ( svFlags & SV_FLAG_HARDCORE )
31400 {
31401 val *= 2;
31402 }
31403 if ( skillCapstoneUnlocked(playernum, proficiency) )
31404 {
31405 val = 0.0;
31406 }
31407 if ( val > 0.0001 )
31408 {
31409 val = 100 / val;
31410 }
31411 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31412 }
31413 else if ( tag == "POLEARM_DEGRADE0_CHANCE" )
31414 {
31415 val = 4 + (stats[playernum]->type == GOBLIN ? 4 : 0); // chance to degrade on 0 dmg
31416 val += static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20);
31417 if ( svFlags & SV_FLAG_HARDCORE )
31418 {
31419 val *= 2;
31420 }
31421 if ( skillCapstoneUnlocked(playernum, proficiency) )
31422 {
31423 val = 0.0;
31424 }
31425 if ( val > 0.0001 )
31426 {
31427 val = 100 / val;
31428 }
31429 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31430 }
31431 return buf;
31432 }
31433 else if ( proficiency == PRO_AXE )
31434 {
31435 if ( tag == "AXE_DMG_RANGE" )
31436 {
31437 if ( proficiency == PRO_POLEARM )
31438 {
31439 val = 100 - (100 - stats[playernum]->getModifiedProficiency(proficiency)) / 3.f; // lowest damage roll
31440 }
31441 else
31442 {
31443 val = 100 - (100 - stats[playernum]->getModifiedProficiency(proficiency)) / 2.f; // lowest damage roll
31444 }
31445 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31446 }
31447 else if ( tag == "AXE_DMG_EFFECTIVENESS" )
31448 {
31449 if ( proficiency == PRO_POLEARM )
31450 {
31451 val = -25 + (stats[playernum]->getModifiedProficiency(proficiency) / 2); // -25% to +25%
31452 }
31453 else
31454 {
31455 val = -25 + (stats[playernum]->getModifiedProficiency(proficiency) / 2); // -25% to +25%
31456 }
31457 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31458 }
31459 else if ( tag == "AXE_DEGRADE_CHANCE" )
31460 {
31461 val = 50 + (stats[playernum]->type == GOBLIN ? 20 : 0); // chance to degrade on > 0 dmg
31462 val += (static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20)) * 10;
31463 if ( svFlags & SV_FLAG_HARDCORE )
31464 {
31465 val *= 2;
31466 }
31467 if ( skillCapstoneUnlocked(playernum, proficiency) )
31468 {
31469 val = 0.0;
31470 }
31471 if ( val > 0.0001 )
31472 {
31473 val = 100 / val;
31474 }
31475 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31476 }
31477 else if ( tag == "AXE_DEGRADE0_CHANCE" )
31478 {
31479 val = 4 + (stats[playernum]->type == GOBLIN ? 4 : 0); // chance to degrade on 0 dmg
31480 val += static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20);
31481 if ( svFlags & SV_FLAG_HARDCORE )
31482 {
31483 val *= 2;
31484 }
31485 if ( skillCapstoneUnlocked(playernum, proficiency) )
31486 {
31487 val = 0.0;
31488 }
31489 if ( val > 0.0001 )
31490 {
31491 val = 100 / val;
31492 }
31493 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31494 }
31495 return buf;
31496 }
31497 else if ( proficiency == PRO_MACE )
31498 {
31499 if ( tag == "MACE_DMG_RANGE" )
31500 {
31501 if ( proficiency == PRO_POLEARM )
31502 {
31503 val = 100 - (100 - stats[playernum]->getModifiedProficiency(proficiency)) / 3.f; // lowest damage roll
31504 }
31505 else
31506 {
31507 val = 100 - (100 - stats[playernum]->getModifiedProficiency(proficiency)) / 2.f; // lowest damage roll
31508 }
31509 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31510 }
31511 else if ( tag == "MACE_DMG_EFFECTIVENESS" )
31512 {
31513 if ( proficiency == PRO_POLEARM )
31514 {
31515 val = -25 + (stats[playernum]->getModifiedProficiency(proficiency) / 2); // -25% to +25%
31516 }
31517 else
31518 {
31519 val = -25 + (stats[playernum]->getModifiedProficiency(proficiency) / 2); // -25% to +25%
31520 }
31521 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31522 }
31523 else if ( tag == "MACE_DEGRADE_CHANCE" )
31524 {
31525 val = 50 + (stats[playernum]->type == GOBLIN ? 20 : 0); // chance to degrade on > 0 dmg
31526 val += (static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20)) * 10;
31527 if ( svFlags & SV_FLAG_HARDCORE )
31528 {
31529 val *= 2;
31530 }
31531 if ( skillCapstoneUnlocked(playernum, proficiency) )
31532 {
31533 val = 0.0;
31534 }
31535 if ( val > 0.0001 )
31536 {
31537 val = 100 / val;
31538 }
31539 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31540 }
31541 else if ( tag == "MACE_DEGRADE0_CHANCE" )
31542 {
31543 val = 4 + (stats[playernum]->type == GOBLIN ? 4 : 0); // chance to degrade on 0 dmg
31544 val += static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20);
31545 if ( svFlags & SV_FLAG_HARDCORE )
31546 {
31547 val *= 2;
31548 }
31549 if ( skillCapstoneUnlocked(playernum, proficiency) )
31550 {
31551 val = 0.0;
31552 }
31553 if ( val > 0.0001 )
31554 {
31555 val = 100 / val;
31556 }
31557 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31558 }
31559 return buf;
31560 }
31561 else if ( proficiency == PRO_SWIMMING )
31562 {
31563 if ( tag == "SWIM_SPEED_TOTAL" )
31564 {
31565 val = (((stats[playernum]->getModifiedProficiency(proficiency) / 100.f) * 50.f) + 50); // water movement speed
31566 if ( stats[playernum]->type == SKELETON )
31567 {
31568 val *= .5;
31569 }
31570 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31571 }
31572 else if ( tag == "SWIM_SPEED_BASE" )
31573 {
31574 val = -50.0; // water movement speed
31575 if ( stats[playernum]->type == SKELETON )
31576 {
31577 val -= 25.0;
31578 }
31579 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31580 }
31581 else if ( tag == "SWIM_SPEED_BONUS" )
31582 {
31583 val = (((stats[playernum]->getModifiedProficiency(proficiency) / 100.f) * 50.f)); // water movement speed
31584 if ( stats[playernum]->type == SKELETON )
31585 {
31586 val *= .5;
31587 }
31588 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31589 }
31590 return buf;
31591 }
31592 else if ( proficiency == PRO_LEADERSHIP )
31593 {
31594 if ( tag == "LEADER_MAX_FOLLOWERS" )
31595 {
31596 val = std::min(8, std::max(4, 2 * (stats[playernum]->getModifiedProficiency(proficiency) / 20))); // max followers
31597 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31598 }
31599 else if ( tag == "LEADER_FOLLOWER_SPEED" )
31600 {
31601 val = 1 + (stats[playernum]->getModifiedProficiency(proficiency) / 20);
31602 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31603 }
31604 else if ( tag == "LEADER_CHARM_MONSTER" )
31605 {
31606 val = 80 + ((statGetCHR(stats[playernum], player) + stats[playernum]->getModifiedProficiency(proficiency)) / 20) * 10;
31607 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31608 }
31609 else if ( tag == "LIST_LEADER_AVAILABLE_FOLLOWERS" )
31610 {
31611 std::string outputList = "";
31612 Monster playerRace = stats[playernum]->type;
31613 std::map<Monster, std::vector<Monster>>* allyTable = &Player::SkillSheet_t::skillSheetData.leadershipAllyTableBase;
31614 enum TableOrder : int
31615 {
31616 LEADER_BASE_TABLE,
31617 LEADER_UNIQUE_TABLE,
31618 LEADER_CAPSTONE_TABLE,
31619 TABLE_MAX
31620 };
31621 for ( int i = LEADER_BASE_TABLE; i < TABLE_MAX; ++i )
31622 {
31623 if ( i == LEADER_CAPSTONE_TABLE )
31624 {
31625 if ( skillCapstoneUnlocked(playernum, proficiency) )
31626 {
31627 allyTable = &Player::SkillSheet_t::skillSheetData.leadershipAllyTableLegendary;
31628 }
31629 else
31630 {
31631 continue;
31632 }
31633 }
31634 if ( i == LEADER_CAPSTONE_TABLE || i == LEADER_BASE_TABLE )
31635 {
31636 if ( allyTable->find(playerRace) != allyTable->end() )
31637 {
31638 if ( !(*allyTable)[playerRace].empty() )
31639 {
31640 for ( auto& ally : (*allyTable)[playerRace] )
31641 {
31642 if ( ally < 0 || ally >= NUMMONSTERS ) { continue; }
31643 std::string monsterName = "";
31644 monsterName = getMonsterLocalizedName(ally);
31645 capitalizeString(monsterName);
31646 if ( outputList != "" )
31647 {
31648 outputList += '\n';
31649 }
31650 outputList += "\x1E " + monsterName;
31651 }
31652 }
31653 }
31654 }
31655 else if ( i == LEADER_UNIQUE_TABLE )
31656 {
31657 auto* allyTable = &Player::SkillSheet_t::skillSheetData.leadershipAllyTableSpecialRecruitment;
31658 if ( allyTable->find(playerRace) != allyTable->end() )
31659 {
31660 if ( !(*allyTable)[playerRace].empty() )
31661 {
31662 for ( auto& allyPair : (*allyTable)[playerRace] )
31663 {
31664 auto& ally = allyPair.first;
31665 if ( ally < 0 || ally >= NUMMONSTERS ) { continue; }
31666 std::string monsterName = "";
31667 monsterName = getMonsterLocalizedName(ally);
31668 capitalizeString(monsterName);
31669 if ( outputList != "" )
31670 {
31671 outputList += '\n';
31672 }
31673 outputList += "\x1E " + monsterName;
31674 outputList += "\n" + allyPair.second;
31675 }
31676 }
31677 }
31678 }
31679 }
31680 if ( outputList == "" ) { outputList = "-"; }
31681 snprintf(buf, sizeof(buf), rawValue.c_str(), outputList.c_str());
31682 }
31683 return buf;
31684 }
31685 else if ( proficiency == PRO_TRADING )
31686 {
31687 if ( tag == "TRADING_BUY_PRICE" )
31688 {
31689 val = 1 / ((50 + stats[playernum]->getModifiedProficiency(proficiency)) / 150.f); // buy value
31690 //val /= 1.f + statGetCHR(stats[playernum], players[playernum]->entity) / 20.f;
31691 val = std::max(1.0, val);
31692 val = val * 100.0 - 100.0;
31693 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31694 }
31695 else if ( tag == "TRADING_SELL_PRICE" )
31696 {
31697 val = (50 + stats[playernum]->getModifiedProficiency(proficiency)) / 150.f; // sell value
31698 val *= 1.f + statGetCHR(stats[playernum], players[playernum]->entity) / 20.f;
31699 val = std::min(1.0, val);
31700 val = val * 100.0 - 100.0;
31701 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31702 }
31703 return buf;
31704 }
31705 else if ( proficiency == PRO_APPRAISAL )
31706 {
31707 if ( tag == "APPRAISE_GOLD_SPEED" )
31708 {
31709 if ( skillCapstoneUnlocked(playernum, proficiency) )
31710 {
31711 snprintf(buf, sizeof(buf), "%s", Language::get(4064)); // "instant"
31712 }
31713 else
31714 {
31715 val = (60.f / (stats[playernum]->getModifiedProficiency(proficiency) + 1)) / (TICKS_PER_SECOND50); // appraisal time per gold value
31716 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31717 }
31718 }
31719 else if ( tag == "APPRAISE_MAX_GOLD_VALUE" )
31720 {
31721 if ( skillCapstoneUnlocked(playernum, proficiency) )
31722 {
31723 snprintf(buf, sizeof(buf), "%s", Language::get(4065)); // "any"
31724 }
31725 else
31726 {
31727 val = 10 * (stats[playernum]->getModifiedProficiency(proficiency) + (statGetPER(stats[playernum], player) * 5)); // max gold value can appraise
31728 if ( val < 0.0 )
31729 {
31730 snprintf(buf, sizeof(buf), "??? Gold");
31731 }
31732 else if ( val < 0.1 )
31733 {
31734 val = 9;
31735 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31736 }
31737 else
31738 {
31739 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31740 }
31741 }
31742 }
31743 else if ( tag == "APPRAISE_WORTHLESS_GLASS" )
31744 {
31745 if ( (stats[playernum]->getModifiedProficiency(proficiency) + (statGetPER(stats[playernum], player) * 5)) >= 100 )
31746 {
31747 snprintf(buf, sizeof(buf), rawValue.c_str(), Language::get(1314)); // yes
31748 }
31749 else
31750 {
31751 snprintf(buf, sizeof(buf), rawValue.c_str(), Language::get(1315)); // no
31752 }
31753 }
31754 return buf;
31755 }
31756 else if ( proficiency == PRO_LOCKPICKING )
31757 {
31758 Sint32 PER = statGetPER(stats[playernum], player);
31759 if ( tag == "TINKERING_LOCKPICK_CHESTS_DOORS" )
31760 {
31761 val = stats[playernum]->getModifiedProficiency(proficiency) / 2.f; // lockpick chests/doors
31762 if ( stats[playernum]->getModifiedProficiency(proficiency) >= SKILL_LEVEL_LEGENDARY )
31763 {
31764 val = 100.f;
31765 }
31766 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31767 }
31768 else if ( tag == "TINKERING_SCRAP_CHESTS" )
31769 {
31770 val = std::min(100.f, stats[playernum]->getModifiedProficiency(proficiency) + 50.f);
31771 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31772 }
31773 else if ( tag == "TINKERING_SCRAP_AUTOMATONS" )
31774 {
31775 if ( stats[playernum]->getModifiedProficiency(proficiency) >= SKILL_LEVEL_EXPERT )
31776 {
31777 val = 100.f; // lockpick automatons
31778 }
31779 else
31780 {
31781 val = (100 - 100 / (static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20 + 1))); // lockpick automatons
31782 }
31783 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31784 }
31785 else if ( tag == "TINKERING_DISARM_ARROWS" )
31786 {
31787 val = (100 - 100 / (std::max(1, static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 10)))); // disarm arrow traps
31788 if ( stats[playernum]->getModifiedProficiency(proficiency) < SKILL_LEVEL_BASIC )
31789 {
31790 val = 0.f;
31791 }
31792 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31793 }
31794 else if ( tag == "TINKERING_KIT_SCRAP_BONUS" )
31795 {
31796 // bonus scrapping chances.
31797 int skillLVL = std::min(5, static_cast<int>((stats[playernum]->getModifiedProficiency(proficiency) + PER) / 20));
31798 skillLVL = std::max(0, skillLVL);
31799 switch ( skillLVL )
31800 {
31801 case 5:
31802 val = 150.f;
31803 break;
31804 case 4:
31805 val = 125.f;
31806 break;
31807 case 3:
31808 val = 50.f;
31809 break;
31810 case 2:
31811 val = 25.f;
31812 break;
31813 case 1:
31814 val = 12.5;
31815 break;
31816 default:
31817 val = 0.f;
31818 break;
31819 }
31820 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31821 }
31822 else if ( tag == "TINKERING_KIT_REPAIR_ITEM" )
31823 {
31824 std::string canRepairItems = Language::get(4057); // none
31825 char metalbuf[64] = "";
31826 char magicbuf[64] = "";
31827 if ( (stats[playernum]->getModifiedProficiency(proficiency) + PER + (stats[playernum]->type == AUTOMATON ? 20 : 0)) >= SKILL_LEVEL_LEGENDARY )
31828 {
31829 canRepairItems = Language::get(4058); // all
31830 }
31831 else if ( (stats[playernum]->getModifiedProficiency(proficiency) + PER + (stats[playernum]->type == AUTOMATON ? 20 : 0)) >= SKILL_LEVEL_MASTER )
31832 {
31833 // 2/0
31834 snprintf(metalbuf, sizeof(metalbuf), Language::get(4059), 2);
31835 snprintf(magicbuf, sizeof(magicbuf), Language::get(4060), 0);
31836 canRepairItems = "\x1E ";
31837 canRepairItems += metalbuf;
31838 canRepairItems += '\n';
31839 canRepairItems += "\x1E ";
31840 canRepairItems += magicbuf;
31841 }
31842 else if ( (stats[playernum]->getModifiedProficiency(proficiency) + PER + (stats[playernum]->type == AUTOMATON ? 20 : 0)) >= SKILL_LEVEL_EXPERT )
31843 {
31844 // 1/0
31845 snprintf(metalbuf, sizeof(metalbuf), Language::get(4059), 1);
31846 snprintf(magicbuf, sizeof(magicbuf), Language::get(4060), 0);
31847 canRepairItems = "\x1E ";
31848 canRepairItems += metalbuf;
31849 canRepairItems += '\n';
31850 canRepairItems += "\x1E ";
31851 canRepairItems += magicbuf;
31852 }
31853 snprintf(buf, sizeof(buf), rawValue.c_str(), canRepairItems.c_str());
31854 }
31855 else if ( tag == "TINKERING_MAX_ALLIES" )
31856 {
31857 val = maximumTinkeringBotsCanBeDeployed(stats[playernum]);
31858 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31859 }
31860 return buf;
31861 }
31862 else if ( proficiency == PRO_ALCHEMY )
31863 {
31864 if ( tag == "ALCHEMY_POTION_EFFECT_DMG" )
31865 {
31866 int skillLVL = stats[playernum]->getModifiedProficiency(proficiency) / 20;
31867 // +0% baseline
31868 val = 100 * (potionDamageSkillMultipliers[std::min(skillLVL, 5)] - potionDamageSkillMultipliers[0]);
31869 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31870 }
31871 else if ( tag == "ALCHEMY_THROWN_IMPACT_DMG" )
31872 {
31873 int skillLVL = stats[playernum]->getModifiedProficiency(proficiency) / 20;
31874 // +0% baseline
31875 val = 100 * (potionDamageSkillMultipliers[std::min(skillLVL, 5)] - potionDamageSkillMultipliers[0]);
31876 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31877 }
31878 else if ( tag == "ALCHEMY_DUPLICATION_CHANCE" )
31879 {
31880 val = 50.f + static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20) * 10;
31881 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31882 }
31883 else if ( tag == "ALCHEMY_EMPTY_BOTTLE_CONSUME" )
31884 {
31885 val = std::min(80, (60 + static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20) * 10));
31886 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31887 }
31888 else if ( tag == "ALCHEMY_EMPTY_BOTTLE_BREW" )
31889 {
31890 val = 50.f + static_cast<int>(stats[playernum]->getModifiedProficiency(proficiency) / 20) * 5;
31891 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31892 }
31893 else if ( tag == "ALCHEMY_LEARNT_INGREDIENTS_BASE" )
31894 {
31895 std::string outputList = "";
31896 for ( auto it = clientLearnedAlchemyIngredients[playernum].begin();
31897 it != clientLearnedAlchemyIngredients[playernum].end(); ++it )
31898 {
31899 auto alchemyEntry = *it;
31900 if ( GenericGUI[playernum].isItemBaseIngredient(alchemyEntry) )
31901 {
31902 std::string itemName = items[alchemyEntry].getIdentifiedName();
31903 size_t pos = std::string::npos;
31904 for ( auto& potionName : Player::SkillSheet_t::skillSheetData.potionNamesToFilter )
31905 {
31906 if ( (pos = itemName.find(potionName)) != std::string::npos )
31907 {
31908 itemName.erase(pos, potionName.length());
31909 }
31910 }
31911 capitalizeString(itemName);
31912 if ( outputList != "" )
31913 {
31914 outputList += '\n';
31915 }
31916 outputList += "\x1E " + itemName;
31917 }
31918 }
31919 if ( outputList == "" ) { outputList = "-"; }
31920 snprintf(buf, sizeof(buf), rawValue.c_str(), outputList.c_str());
31921 }
31922 else if ( tag == "ALCHEMY_LEARNT_INGREDIENTS_SECONDARY" )
31923 {
31924 std::string outputList = "";
31925 for ( auto it = clientLearnedAlchemyIngredients[playernum].begin();
31926 it != clientLearnedAlchemyIngredients[playernum].end(); ++it )
31927 {
31928 auto alchemyEntry = *it;
31929 if ( GenericGUI[playernum].isItemSecondaryIngredient(alchemyEntry)
31930 && !GenericGUI[playernum].isItemBaseIngredient(alchemyEntry) )
31931 {
31932 std::string itemName = items[alchemyEntry].getIdentifiedName();
31933 size_t pos = std::string::npos;
31934 for ( auto& potionName : Player::SkillSheet_t::skillSheetData.potionNamesToFilter )
31935 {
31936 if ( (pos = itemName.find(potionName)) != std::string::npos )
31937 {
31938 itemName.erase(pos, potionName.length());
31939 }
31940 }
31941 capitalizeString(itemName);
31942 if ( outputList != "" )
31943 {
31944 outputList += '\n';
31945 }
31946 outputList += "\x1E " + itemName;
31947 }
31948 }
31949 if ( outputList == "" ) { outputList = "-"; }
31950 snprintf(buf, sizeof(buf), rawValue.c_str(), outputList.c_str());
31951 }
31952 return buf;
31953 }
31954 else if ( proficiency == PRO_SPELLCASTING )
31955 {
31956 if ( tag == "CASTING_MP_REGEN" )
31957 {
31958 if ( (stats[playernum])->playerRace == RACE_INSECTOID && (stats[playernum])->appearance == 0 )
31959 {
31960 return Language::get(4066);
31961 }
31962 else if ( (stats[playernum])->type == AUTOMATON )
31963 {
31964 return Language::get(4067);
31965 }
31966 else
31967 {
31968 val = getBaseManaRegen(player, *(stats[playernum])) / (TICKS_PER_SECOND50 * 1.f);
31969 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
31970 }
31971 }
31972 else if ( tag == "CASTING_MP_REGEN_SKILL_MULTIPLIER" )
31973 {
31974 if ( (stats[playernum])->playerRace == RACE_INSECTOID && (stats[playernum])->appearance == 0 )
31975 {
31976 return Language::get(4066);
31977 }
31978 else if ( (stats[playernum])->type == AUTOMATON )
31979 {
31980 return Language::get(4067);
31981 }
31982 else
31983 {
31984 val = 0.0;
31985 int skill = stats[playernum]->getModifiedProficiency(proficiency);
31986 int multiplier = (skill / 20) + 1;
31987 val = multiplier;
31988 //real_t normalValue = player->getBaseManaRegen(*(stats[playernum])) / (TICKS_PER_SECOND * 1.f);
31989 //stats[playernum]->getModifiedProficiency(proficiency) = 0;
31990 //real_t zeroValue = player->getBaseManaRegen(*(stats[playernum])) / (TICKS_PER_SECOND * 1.f);
31991 //stats[playernum]->getModifiedProficiency(proficiency) = skill;
31992 //
31993 //val = (100 * zeroValue / normalValue) - 100;
31994 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
31995 }
31996 }
31997 else if ( tag == "CASTING_MP_REGEN_SKILL_BONUS" )
31998 {
31999 val = 0.0;
32000 int skill = stats[playernum]->getProficiency(proficiency);
32001 real_t normalValue = getBaseManaRegen(player, *(stats[playernum])) / (TICKS_PER_SECOND50 * 1.f);
32002 stats[playernum]->setProficiencyUnsafe(proficiency, -999);
32003 real_t zeroValue = getBaseManaRegen(player, *(stats[playernum])) / (TICKS_PER_SECOND50 * 1.f);
32004 stats[playernum]->setProficiency(proficiency, skill);
32005
32006 val = (100 * zeroValue / normalValue) - 100;
32007 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
32008 }
32009 else if ( tag == "CASTING_MP_REGEN_BONUS_INT" )
32010 {
32011 val = 0.0;
32012 int stat = stats[playernum]->INT;
32013 real_t normalValue = getBaseManaRegen(player, *(stats[playernum])) / (TICKS_PER_SECOND50 * 1.f);
32014 stats[playernum]->INT = 0;
32015 real_t zeroValue = getBaseManaRegen(player, *(stats[playernum])) / (TICKS_PER_SECOND50 * 1.f);
32016 stats[playernum]->INT = stat;
32017
32018 val = (100 * zeroValue / normalValue) - 100;
32019 snprintf(buf, sizeof(buf), rawValue.c_str(), val);
32020 }
32021 else if ( tag == "CASTING_BEGINNER" )
32022 {
32023 if ( isSpellcasterBeginner(playernum, player) )
32024 {
32025 snprintf(buf, sizeof(buf), rawValue.c_str(), Language::get(1314)); // yes
32026 }
32027 else
32028 {
32029 snprintf(buf, sizeof(buf), rawValue.c_str(), Language::get(1315)); // no
32030 }
32031 }
32032 else if ( tag == "CASTING_SPELLBOOK_FUMBLE" )
32033 {
32034 int skillLVL = std::min(std::max(0, stats[playernum]->getModifiedProficiency(proficiency) + statGetINT(stats[playernum], player)), 100);
32035 skillLVL /= 20;
32036 std::string tierName = Language::get(4061);
32037 tierName += " ";
32038 if ( skillLVL <= 0 )
32039 {
32040 tierName += "I";
32041 }
32042 else if ( skillLVL == 1 )
32043 {
32044 tierName += "II";
32045 }
32046 else if ( skillLVL == 2 )
32047 {
32048 tierName += "III";
32049 }
32050 else if ( skillLVL == 3 )
32051 {
32052 tierName += "IV";
32053 }
32054 else if ( skillLVL == 4 )
32055 {
32056 tierName += "V";
32057 }
32058 else if ( skillLVL >= 5 )
32059 {
32060 tierName += "VI";
32061 }
32062 snprintf(buf, sizeof(buf), rawValue.c_str(), tierName.c_str());
32063 }
32064 return buf;
32065 }
32066 else if ( proficiency == PRO_MAGIC )
32067 {
32068 if ( tag == "MAGIC_CURRENT_TIER" )
32069 {
32070 std::string tierName = Language::get(4061);
32071 int skillLVL = std::min(stats[playernum]->getModifiedProficiency(proficiency) + statGetINT(stats[playernum], player), 100);
32072 if ( skillLVL < 0 )
32073 {
32074 tierName = Language::get(4057); // none
32075 }
32076 else
32077 {
32078 skillLVL /= 20;
32079 tierName += " ";
32080 if ( skillLVL == 0 )
32081 {
32082 tierName += "I";
32083 }
32084 else if ( skillLVL == 1 )
32085 {
32086 tierName += "II";
32087 }
32088 else if ( skillLVL == 2 )
32089 {
32090 tierName += "III";
32091 }
32092 else if ( skillLVL == 3 )
32093 {
32094 tierName += "IV";
32095 }
32096 else if ( skillLVL == 4 )
32097 {
32098 tierName += "V";
32099 }
32100 else if ( skillLVL >= 5 )
32101 {
32102 tierName += "VI";
32103 }
32104 }
32105 snprintf(buf, sizeof(buf), rawValue.c_str(), tierName.c_str());
32106 }
32107 else if ( tag == "MAGIC_SPELLPOWER_TOTAL" )
32108 {
32109 val = (getBonusFromCasterOfSpellElement(player, stats[playernum], nullptr, SPELL_NONE) * 100.0);
32110 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
32111 }
32112 else if ( tag == "MAGIC_SPELLPOWER_INT" )
32113 {
32114 //val = (getBonusFromCasterOfSpellElement(player, stats[playernum], nullptr, SPELL_NONE) * 100.0);
32115 int INT = statGetINT(stats[playernum], players[playernum]->entity);
32116 real_t bonus = 0.0;
32117 if ( INT > 0 )
32118 {
32119 bonus += INT / 100.0;
32120 }
32121 val = bonus * 100.0;
32122 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
32123 }
32124 else if ( tag == "MAGIC_SPELLPOWER_EQUIPMENT" )
32125 {
32126 val = (getBonusFromCasterOfSpellElement(player, stats[playernum], nullptr, SPELL_NONE) * 100.0);
32127 int INT = statGetINT(stats[playernum], players[playernum]->entity);
32128 real_t bonus = 0.0;
32129 if ( INT > 0 )
32130 {
32131 bonus += INT / 100.0;
32132 }
32133 val -= bonus * 100.0;
32134 snprintf(buf, sizeof(buf), rawValue.c_str(), (int)val);
32135 }
32136 else if ( tag == "MAGIC_CURRENT_TIER_SPELLS" )
32137 {
32138 int skillLVL = std::min(stats[playernum]->getModifiedProficiency(proficiency) + statGetINT(stats[playernum], player), 100);
32139 if ( skillLVL >= 0 )
32140 {
32141 skillLVL /= 20;
32142 }
32143 std::string magics = "";
32144 for ( auto it = allGameSpells.begin(); it != allGameSpells.end(); ++it )
32145 {
32146 auto spellEntry = *it;
32147 if ( spellEntry->ID == SPELL_WEAKNESS || spellEntry->ID == SPELL_GHOST_BOLT )
32148 {
32149 continue;
32150 }
32151 if ( spellEntry && spellEntry->difficulty == (skillLVL * 20) )
32152 {
32153 if ( magics != "" )
32154 {
32155 magics += '\n';
32156 }
32157 magics += "\x1E ";
32158 magics += spellEntry->getSpellName();
32159 }
32160 }
32161 if ( magics == "" ) { magics = "-"; }
32162 snprintf(buf, sizeof(buf), rawValue.c_str(), magics.c_str());
32163 }
32164 return buf;
32165 }
32166
32167 return "";
32168}
32169
32170void Player::SkillSheet_t::selectSkill(int skill)
32171{
32172 selectedSkill = skill;
32173 bSkillSheetEntryLoaded = false;
32174 openTick = ticks;
32175 resetSkillDisplay();
32176}
32177
32178//void positionSkillSheetBlitField(Field* f, Text* tex, SDL_Rect& pos, int yoff = 0)
32179//{
32180// Font* actualFont = Font::get(f->getFont());
32181// int lines = std::max(1, tex->getNumTextLines());
32182// int fullH = lines * (actualFont->height(false) + actualFont->getOutline() * 2);
32183// if ( f->getVJustify() == Field::justify_t::TOP )
32184// {
32185// pos.y = pos.y + yoff + std::min(pos.h - fullH, 0);
32186// }
32187// else if ( f->getVJustify() == Field::justify_t::CENTER )
32188// {
32189// pos.y = pos.y + yoff + (pos.h - fullH) / 2;
32190// }
32191// if ( f->getHJustify() == Field::justify_t::RIGHT )
32192// {
32193// pos.x = pos.x + pos.w - tex->getWidth();
32194// }
32195// else if ( f->getHJustify() == Field::justify_t::CENTER )
32196// {
32197// pos.x = pos.x + pos.w / 2 - tex->getWidth() / 2;
32198// }
32199//}
32200//
32201//SDL_Surface* blitSkillSheet(Frame* skillsFrame)
32202//{
32203// int player = skillsFrame->getOwner();
32204// SDL_Surface* sprite = SDL_CreateRGBSurface(0, skillsFrame->getSize().w, skillsFrame->getSize().h, 32,
32205// 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
32206//
32207// real_t opacity = 1.0; // skillsFrame->getOpacity() / 100.0
32208//
32209// SDL_Rect totalPos = skillSheetEntryFrames[player].skillsFrame->getSize();
32210// /*for ( auto& img : skillSheetEntryFrames[player].entryFrameLeft->getImages() )
32211// {
32212// if ( img->disabled ) { continue; }
32213// if ( img->path == "" ) { continue; }
32214// SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
32215// Uint8 r, g, b, a;
32216// getColor(img->color, &r, &g, &b, &a);
32217// SDL_SetSurfaceAlphaMod(srcSurf, a * opacity);
32218// SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32219// SDL_Rect pos = img->pos;
32220// pos.x += totalPos.x;
32221// pos.y += totalPos.y;
32222// pos.x += skillSheetEntryFrames[player].entryFrameLeft->getSize().x;
32223// pos.y += skillSheetEntryFrames[player].entryFrameLeft->getSize().y;
32224// SDL_BlitScaled(srcSurf, nullptr, sprite, &pos);
32225// }
32226//
32227// for ( auto& img : skillSheetEntryFrames[player].entryFrameRight->getImages() )
32228// {
32229// if ( img->disabled ) { continue; }
32230// if ( img->path == "" ) { continue; }
32231// SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
32232// Uint8 r, g, b, a;
32233// getColor(img->color, &r, &g, &b, &a);
32234// SDL_SetSurfaceAlphaMod(srcSurf, a * opacity);
32235// SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32236// SDL_Rect pos = img->pos;
32237// pos.x += totalPos.x;
32238// pos.y += totalPos.y;
32239// pos.x += skillSheetEntryFrames[player].entryFrameRight->getSize().x;
32240// pos.y += skillSheetEntryFrames[player].entryFrameRight->getSize().y;
32241// SDL_BlitScaled(srcSurf, nullptr, sprite, &pos);
32242// }*/
32243//
32244// for ( auto frame : skillSheetEntryFrames[player].skillsFrame->getFrames() )
32245// {
32246// totalPos = skillSheetEntryFrames[player].skillsFrame->getSize();
32247//
32248// totalPos.x += frame->getSize().x;
32249// totalPos.y += frame->getSize().y;
32250// for ( auto& img : frame->getImages() )
32251// {
32252// if ( img->disabled ) { continue; }
32253// if ( img->path == "" ) { continue; }
32254// if ( img->ontop ) { continue; }
32255// SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
32256// Uint8 r, g, b, a;
32257// getColor(img->color, &r, &g, &b, &a);
32258// SDL_SetSurfaceAlphaMod(srcSurf, a * opacity);
32259// //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32260// SDL_Rect pos = img->pos;
32261// pos.x += totalPos.x;
32262// pos.y += totalPos.y;
32263// SDL_BlitScaled(srcSurf, nullptr, sprite, &pos);
32264// }
32265// }
32266//
32267// for ( int i = 0; i < NUMPROFICIENCIES; ++i )
32268// {
32269// Frame* frame = skillSheetEntryFrames[player].entryFrames[i];
32270// totalPos = skillSheetEntryFrames[player].skillsFrame->getSize();
32271// totalPos.x += frame->getSize().x;
32272// if ( i >= 8 )
32273// {
32274// totalPos.x += skillSheetEntryFrames[player].entryFrameLeft->getSize().x;
32275// }
32276// else
32277// {
32278// totalPos.x += skillSheetEntryFrames[player].entryFrameRight->getSize().x;
32279// }
32280// totalPos.y += frame->getSize().y;
32281// if ( i >= 8 )
32282// {
32283// totalPos.y += skillSheetEntryFrames[player].entryFrameLeft->getSize().y;
32284// }
32285// else
32286// {
32287// totalPos.y += skillSheetEntryFrames[player].entryFrameRight->getSize().y;
32288// }
32289// for ( auto img : frame->getImages() )
32290// {
32291// if ( img->disabled ) { continue; }
32292// if ( img->path == "" ) { continue; }
32293// if ( img->ontop ) { continue; }
32294// SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
32295// Uint8 r, g, b, a;
32296// getColor(img->color, &r, &g, &b, &a);
32297// SDL_SetSurfaceAlphaMod(srcSurf, a * opacity);
32298// //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32299// SDL_Rect pos = img->pos;
32300// pos.x += totalPos.x;
32301// pos.y += totalPos.y;
32302// SDL_BlitScaled(srcSurf, nullptr, sprite, &pos);
32303// }
32304//
32305// for ( auto img : frame->getImages() )
32306// {
32307// if ( img->disabled ) { continue; }
32308// if ( img->path == "" ) { continue; }
32309// if ( !img->ontop ) { continue; }
32310// SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
32311// Uint8 r, g, b, a;
32312// getColor(img->color, &r, &g, &b, &a);
32313// SDL_SetSurfaceAlphaMod(srcSurf, a * opacity);
32314// //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32315// SDL_Rect pos = img->pos;
32316// pos.x += totalPos.x;
32317// pos.y += totalPos.y;
32318// SDL_BlitScaled(srcSurf, nullptr, sprite, &pos);
32319// }
32320//
32321// for ( auto f : frame->getFields() )
32322// {
32323// auto tex = f->getTextObject();
32324// SDL_Surface* srcSurf = const_cast<SDL_Surface*>(tex->getSurf());
32325// SDL_SetSurfaceAlphaMod(srcSurf, 255 * opacity);
32326// //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32327// SDL_Rect pos = f->getSize();
32328//
32329// positionSkillSheetBlitField(f, tex, pos);
32330//
32331// pos.x += totalPos.x;
32332// pos.y += totalPos.y;
32333// SDL_BlitSurface(srcSurf, nullptr, sprite, &pos);
32334// }
32335// }
32336//
32337// for ( auto frame : skillSheetEntryFrames[player].skillsFrame->getFrames() )
32338// {
32339// totalPos = skillSheetEntryFrames[player].skillsFrame->getSize();
32340//
32341// totalPos.x += frame->getSize().x;
32342// totalPos.y += frame->getSize().y;
32343// for ( auto& img : frame->getImages() )
32344// {
32345// if ( img->disabled ) { continue; }
32346// if ( img->path == "" ) { continue; }
32347// if ( !img->ontop ) { continue; }
32348// SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
32349// Uint8 r, g, b, a;
32350// getColor(img->color, &r, &g, &b, &a);
32351// SDL_SetSurfaceAlphaMod(srcSurf, a * opacity);
32352// //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32353// SDL_Rect pos = img->pos;
32354// pos.x += totalPos.x;
32355// pos.y += totalPos.y;
32356// SDL_BlitScaled(srcSurf, nullptr, sprite, &pos);
32357// }
32358// }
32359//
32360// //for ( auto img : skillsFrame->getImages() )
32361// //{
32362// // if ( img->disabled ) { continue; }
32363// // if ( img->path == "" ) { continue; }
32364// // SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
32365// // Uint8 r, g, b, a;
32366// // getColor(img->color, &r, &g, &b, &a);
32367// // SDL_SetSurfaceAlphaMod(srcSurf, a * opacity);
32368// // //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32369// // SDL_Rect pos = img->pos;
32370// // SDL_BlitScaled(srcSurf, nullptr, sprite, &pos);
32371// //}
32372//
32373// for ( auto frame : skillsFrame->getFrames() )
32374// {
32375// totalPos = skillSheetEntryFrames[player].skillsFrame->getSize();
32376//
32377// for ( auto f : frame->getFields() )
32378// {
32379// auto tex = f->getTextObject();
32380// SDL_Surface* srcSurf = const_cast<SDL_Surface*>(tex->getSurf());
32381// SDL_SetSurfaceAlphaMod(srcSurf, 255 * opacity);
32382// //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32383// SDL_Rect pos = f->getSize();
32384//
32385// positionSkillSheetBlitField(f, tex, pos);
32386//
32387// pos.x += totalPos.x;
32388// pos.y += totalPos.y;
32389// SDL_BlitSurface(srcSurf, nullptr, sprite, &pos);
32390// }
32391// }
32392//
32393// for ( auto frame : skillSheetEntryFrames[player].skillDescFrame->getFrames() )
32394// {
32395// totalPos = skillSheetEntryFrames[player].skillsFrame->getSize();
32396// totalPos.x += skillSheetEntryFrames[player].skillDescFrame->getSize().x;
32397// totalPos.y += skillSheetEntryFrames[player].skillDescFrame->getSize().y;
32398//
32399// totalPos.x += frame->getSize().x;
32400// totalPos.y += frame->getSize().y;
32401// for ( auto& img : frame->getImages() )
32402// {
32403// if ( img->disabled ) { continue; }
32404// if ( img->path == "" ) { continue; }
32405// //if ( !img->ontop ) { continue; }
32406// SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
32407// Uint8 r, g, b, a;
32408// getColor(img->color, &r, &g, &b, &a);
32409// SDL_SetSurfaceAlphaMod(srcSurf, a * opacity);
32410// //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32411// SDL_Rect pos = img->pos;
32412// pos.x += totalPos.x;
32413// pos.y += totalPos.y;
32414// SDL_BlitScaled(srcSurf, nullptr, sprite, &pos);
32415// }
32416//
32417// for ( auto f : frame->getFields() )
32418// {
32419// auto tex = f->getTextObject();
32420// SDL_Surface* srcSurf = const_cast<SDL_Surface*>(tex->getSurf());
32421// SDL_SetSurfaceAlphaMod(srcSurf, 255 * opacity);
32422// //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32423// SDL_Rect pos = f->getSize();
32424//
32425// positionSkillSheetBlitField(f, tex, pos);
32426//
32427// pos.x += totalPos.x;
32428// pos.y += totalPos.y;
32429// SDL_BlitSurface(srcSurf, nullptr, sprite, &pos);
32430// }
32431// }
32432//
32433// for ( auto frame : skillSheetEntryFrames[player].scrollAreaOuterFrame->getFrames() )
32434// {
32435// totalPos = skillSheetEntryFrames[player].skillsFrame->getSize();
32436// totalPos.x += skillSheetEntryFrames[player].skillDescFrame->getSize().x;
32437// totalPos.y += skillSheetEntryFrames[player].skillDescFrame->getSize().y;
32438// totalPos.x += skillSheetEntryFrames[player].scrollAreaOuterFrame->getSize().x;
32439// totalPos.y += skillSheetEntryFrames[player].scrollAreaOuterFrame->getSize().y;
32440//
32441// totalPos.x += frame->getSize().x;
32442// totalPos.y += frame->getSize().y;
32443// for ( auto& img : frame->getImages() )
32444// {
32445// if ( img->disabled ) { continue; }
32446// if ( img->path == "" ) { continue; }
32447// //if ( !img->ontop ) { continue; }
32448// SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
32449// Uint8 r, g, b, a;
32450// getColor(img->color, &r, &g, &b, &a);
32451// SDL_SetSurfaceAlphaMod(srcSurf, a * opacity);
32452// //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32453// SDL_Rect pos = img->pos;
32454// pos.x += totalPos.x;
32455// pos.y += totalPos.y;
32456// SDL_BlitScaled(srcSurf, nullptr, sprite, &pos);
32457// }
32458//
32459// for ( auto f : frame->getFields() )
32460// {
32461// if ( f->isDisabled() ) { continue; }
32462// auto tex = f->getTextObject();
32463// SDL_Surface* srcSurf = const_cast<SDL_Surface*>(tex->getSurf());
32464// SDL_SetSurfaceAlphaMod(srcSurf, 255 * opacity);
32465// //SDL_SetSurfaceBlendMode(srcSurf, SDL_BLENDMODE_NONE);
32466// SDL_Rect pos = f->getSize();
32467//
32468// if ( tex->getNumTextLines() > 1 )
32469// {
32470// char* buf = (char*)malloc(f->getTextLen() + 1);
32471// memcpy(buf, f->getText(), f->getTextLen() + 1);
32472//
32473// int yoff = 0;
32474// int currentLine = -1;
32475// char* nexttoken;
32476// char* token = buf;
32477// do {
32478// ++currentLine;
32479// nexttoken = Field::tokenize(token, "\n");
32480//
32481// Text* text = Text::get(token, f->getFont(), f->getTextColor(), f->getOutlineColor());
32482// assert(text);
32483//
32484// positionSkillSheetBlitField(f, text, pos, yoff);
32485// pos.x += totalPos.x;
32486// pos.y += totalPos.y;
32487//
32488// srcSurf = const_cast<SDL_Surface*>(text->getSurf());
32489// SDL_SetSurfaceAlphaMod(srcSurf, 255 * opacity);
32490//
32491// SDL_Rect srcPos = pos;
32492// srcPos.x = 0;
32493// srcPos.y = 0;
32494// if ( f->getSize().y + yoff + frame->getSize().y < 0 )
32495// {
32496// int diff = f->getSize().y + yoff + frame->getSize().y;
32497// srcPos.y -= diff;
32498// pos.y -= diff;
32499// }
32500// else if ( f->getSize().y + yoff + text->getHeight() - (frame->getSize().h - frame->getSize().y) < 0 )
32501// {
32502// int diff = f->getSize().y + yoff + text->getHeight() - (frame->getSize().h - frame->getSize().y);
32503// srcPos.y -= diff;
32504// pos.y -= diff;
32505// }
32506// SDL_BlitSurface(srcSurf, &srcPos, sprite, &pos);
32507//
32508// Font* actualFont = Font::get(f->getFont());
32509// yoff += actualFont->height(true);
32510// pos = f->getSize();
32511// } while ( (token = nexttoken) != NULL );
32512//
32513// free(buf);
32514// }
32515// else
32516// {
32517// positionSkillSheetBlitField(f, tex, pos);
32518// pos.x += totalPos.x;
32519// pos.y += totalPos.y;
32520// SDL_BlitSurface(srcSurf, nullptr, sprite, &pos);
32521// }
32522// }
32523// }
32524//
32525// return sprite;
32526//}
32527
32528void buttonSkillsheetUpdateSelectorOnHighlight(const int player, Button* button)
32529{
32530 if ( button->isHighlighted() )
32531 {
32532 players[player]->GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_SKILLS_LIST);
32533 if ( players[player]->GUI.activeModule != Player::GUI_t::MODULE_SKILLS_LIST )
32534 {
32535 players[player]->GUI.activateModule(Player::GUI_t::MODULE_SKILLS_LIST);
32536 }
32537 SDL_Rect pos = button->getAbsoluteSize();
32538 // make sure to adjust absolute size to camera viewport
32539 pos.x -= players[player]->camera_virtualx1();
32540 pos.y -= players[player]->camera_virtualy1();
32541 players[player]->hud.setCursorDisabled(false);
32542 players[player]->hud.updateCursorAnimation(pos.x - 1, pos.y - 1, pos.w, pos.h, inputs.getVirtualMouse(player)->draw_cursor);
32543 }
32544}
32545
32546void sliderSkillsheetUpdateSelectorOnHighlight(const int player, Slider* slider)
32547{
32548 if ( slider->isHighlighted() )
32549 {
32550 players[player]->GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_SKILLS_LIST);
32551 if ( players[player]->GUI.activeModule != Player::GUI_t::MODULE_SKILLS_LIST )
32552 {
32553 players[player]->GUI.activateModule(Player::GUI_t::MODULE_SKILLS_LIST);
32554 }
32555 SDL_Rect pos = slider->getAbsoluteSize();
32556 // make sure to adjust absolute size to camera viewport
32557 pos.x -= players[player]->camera_virtualx1();
32558 pos.y -= players[player]->camera_virtualy1();
32559 players[player]->hud.setCursorDisabled(false);
32560 players[player]->hud.updateCursorAnimation(pos.x - 1, pos.y - 1, pos.w, pos.h, inputs.getVirtualMouse(player)->draw_cursor);
32561 }
32562}
32563
32564static ConsoleVariable<bool> cvar_skillsheet_optimise("/skillsheet_optimise", true);
32565void Player::SkillSheet_t::processSkillSheet()
32566{
32567 //DebugTimers.addTimePoint("skill 1", "start");
32568 //DebugTimers.addTimePoint("skill", "start");
32569 if ( !skillFrame )
32570 {
32571 createSkillSheet();
32572 }
32573
32574 if ( !player.usingCommand() && Input::inputs[player.playernum].consumeBinaryToggle("Skill Sheet")
32575 && !gamePaused && players[player.playernum]->bControlEnabled )
32576 {
32577 if ( !bSkillSheetOpen )
32578 {
32579 openSkillSheet();
32580 }
32581 else
32582 {
32583 closeSkillSheet();
32584 Player::soundCancel();
32585 }
32586 }
32587
32588 auto oldSkillSlideAmount = skillSlideAmount;
32589 auto oldHighlightedSkill = highlightedSkill;
32590
32591 if ( !bSkillSheetOpen )
32592 {
32593 skillFrame->setDisabled(true);
32594
32595 auto innerFrame = skillFrame->findFrame("skills frame");
32596 SDL_Rect pos = innerFrame->getSize();
32597 skillsFadeInAnimationY = 0.0;
32598 pos.y = -pos.h;
32599 innerFrame->setSize(pos);
32600
32601 auto fade = skillFrame->findImage("fade img");
32602 Uint8 r, g, b, a;
32603 getColor(fade->color, &r, &g, &b, &a);
32604 a = 0;
32605 fade->color = makeColor( r, g, b, a);
32606 fade->disabled = true;
32607 return;
32608 }
32609
32610 bool reblitFrame = false;
32611
32612 skillFrame->setDisabled(false);
32613 skillFrame->setSize(SDL_Rect{ players[player.playernum]->camera_virtualx1(),
32614 players[player.playernum]->camera_virtualy1(),
32615 players[player.playernum]->camera_virtualWidth(),
32616 players[player.playernum]->camera_virtualHeight() });
32617
32618 bool oldCompactViewVal = bUseCompactSkillsView;
32619 bool oldSlideWindowsOnly = bSlideWindowsOnly;
32620 if ( splitscreen && (player.bUseCompactGUIHeight() || player.bUseCompactGUIWidth()) )
32621 {
32622 // use compact view.
32623 if ( player.bUseCompactGUIWidth() && !player.bUseCompactGUIHeight() )
32624 {
32625 bSlideWindowsOnly = true; // 2 player vertical splitscreen, slide but don't use compact view
32626 bUseCompactSkillsView = false;
32627 }
32628 else
32629 {
32630 bUseCompactSkillsView = true;
32631 bSlideWindowsOnly = false;
32632 }
32633 }
32634 else
32635 {
32636 bUseCompactSkillsView = false;
32637 bSlideWindowsOnly = false;
32638 }
32639 if ( (oldCompactViewVal != bUseCompactSkillsView && bUseCompactSkillsView)
32640 || (oldSlideWindowsOnly != bSlideWindowsOnly && bSlideWindowsOnly) )
32641 {
32642 if ( selectedSkill >= 8 )
32643 {
32644 skillSlideAmount = 1.0;
32645 skillSlideDirection = 1;
32646 }
32647 else
32648 {
32649 skillSlideAmount = -1.0;
32650 skillSlideDirection = -1;
32651 }
32652 }
32653 if ( !bUseCompactSkillsView && !bSlideWindowsOnly || (bUseCompactSkillsView && !player.bUseCompactGUIWidth()) )
32654 {
32655 skillSlideAmount = 0.0;
32656 skillSlideDirection = 0;
32657 }
32658
32659 auto innerFrame = skillSheetEntryFrames[player.playernum].skillsFrame;
32660 SDL_Rect sheetSize = innerFrame->getSize();
32661 Frame* allSkillEntriesLeft = skillSheetEntryFrames[player.playernum].entryFrameLeft;
32662 Frame* allSkillEntriesRight = skillSheetEntryFrames[player.playernum].entryFrameRight;
32663 auto leftWingImg = allSkillEntriesLeft->findImage("bg wing left");
32664 auto rightWingImg = allSkillEntriesRight->findImage("bg wing right");
32665 auto skillDescriptionFrame = skillSheetEntryFrames[player.playernum].skillDescFrame;
32666 auto bgImgFrame = skillSheetEntryFrames[player.playernum].skillBgImgsFrame;
32667 auto scrollAreaOuterFrame = skillSheetEntryFrames[player.playernum].scrollAreaOuterFrame;
32668 auto scrollArea = skillSheetEntryFrames[player.playernum].scrollArea;
32669 SDL_Rect scrollOuterFramePos = scrollAreaOuterFrame->getSize();
32670 SDL_Rect scrollAreaPos = scrollArea->getSize();
32671
32672 real_t slideTravelDistance = 52;
32673 int compactViewWidthOffset = (skillSlideDirection != 0 ? 104: 0);
32674 {
32675 // dynamic width/height adjustments of outer containers
32676 sheetSize.h = std::max(0, std::min(skillFrame->getSize().h - 8,
32677 (int)(404 + (bUseCompactSkillsView ? windowCompactHeightScaleY : windowHeightScaleY) * 80)));
32678 sheetSize.w = std::max(0, std::min(skillFrame->getSize().w - 8,
32679 (int)(684 + (bUseCompactSkillsView ? windowCompactHeightScaleX : windowHeightScaleX) * 80)));
32680 if ( player.bUseCompactGUIHeight() && !player.bUseCompactGUIWidth() )
32681 {
32682 sheetSize.w += 12; // some adjustment to match 4 player as it's bigger
32683 }
32684 innerFrame->setSize(sheetSize);
32685
32686 SDL_Rect skillDescPos = skillDescriptionFrame->getSize();
32687 skillDescPos.h = innerFrame->getSize().h - skillDescPos.y - 16;
32688 skillDescPos.w = innerFrame->getSize().w + compactViewWidthOffset - allSkillEntriesLeft->getSize().w - allSkillEntriesRight->getSize().w;
32689 int xMove = innerFrame->getSize().w / 2 - skillDescPos.w / 2;
32690 if ( skillSlideDirection != 0 )
32691 {
32692 xMove += (skillSlideAmount) * slideTravelDistance;
32693 }
32694 if ( xMove != skillDescPos.x )
32695 {
32696 reblitFrame = true;
32697 }
32698 skillDescPos.x = xMove;
32699 skillDescriptionFrame->setSize(skillDescPos);
32700
32701 scrollOuterFramePos.h = skillDescPos.h - 4;
32702
32703 auto leftWingPos = allSkillEntriesLeft->getSize();
32704 auto rightWingPos = allSkillEntriesRight->getSize();
32705
32706 leftWingPos.x = 0;
32707 rightWingPos.x = innerFrame->getSize().w - rightWingPos.w;
32708 leftWingPos.y = std::max(0, innerFrame->getSize().h / 2 - leftWingPos.h / 2);
32709 rightWingPos.y = std::max(0, innerFrame->getSize().h / 2 - rightWingPos.h / 2);
32710 allSkillEntriesLeft->setSize(leftWingPos);
32711 allSkillEntriesRight->setSize(rightWingPos);
32712
32713 auto leftWingAllSkillsImg = allSkillEntriesLeft->findImage("bg wing all skills left");
32714 leftWingAllSkillsImg->ontop = true;
32715 auto rightWingAllSkillsImg = allSkillEntriesRight->findImage("bg wing all skills right");
32716 rightWingAllSkillsImg->ontop = true;
32717 int index = 0;
32718 std::string leftBinary = "00000000";
32719 std::string rightBinary = "00000000";
32720 for ( auto& skillEntry : skillSheetData.skillEntries )
32721 {
32722 if ( index >= 8 )
32723 {
32724 if ( stats[player.playernum]->getModifiedProficiency(skillEntry.skillId) >= SKILL_LEVEL_LEGENDARY )
32725 {
32726 leftBinary[7 - (index - 8)] = '1';
32727 }
32728 }
32729 else
32730 {
32731 if ( stats[player.playernum]->getModifiedProficiency(skillEntry.skillId) >= SKILL_LEVEL_LEGENDARY )
32732 {
32733 rightBinary[7 - index] = '1';
32734 }
32735 }
32736 ++index;
32737 }
32738 if ( bUseCompactSkillsView )
32739 {
32740 leftWingImg->path = "*#images/ui/SkillSheet/UI_Skills_Window_LeftCompact_04.png";
32741 rightWingImg->path = "*#images/ui/SkillSheet/UI_Skills_Window_RightCompact_04.png";
32742
32743 leftWingAllSkillsImg->path = "*#images/ui/SkillSheet/Icons2/UI_Skills_Left_Comp_" + leftBinary + ".png";
32744 rightWingAllSkillsImg->path = "*#images/ui/SkillSheet/Icons2/UI_Skills_Right_Comp_" + rightBinary + ".png";
32745 }
32746 else
32747 {
32748 leftWingImg->path = "*#images/ui/SkillSheet/UI_Skills_Window_Left_04.png";
32749 rightWingImg->path = "*#images/ui/SkillSheet/UI_Skills_Window_Right_04.png";
32750
32751 leftWingAllSkillsImg->path = "*#images/ui/SkillSheet/Icons2/UI_Skills_Left_Full_" + leftBinary + ".png";
32752 rightWingAllSkillsImg->path = "*#images/ui/SkillSheet/Icons2/UI_Skills_Right_Full_" + rightBinary + ".png";
32753 }
32754 if ( auto imgGet = Image::get(leftWingAllSkillsImg->path.c_str()) )
32755 {
32756 if ( bUseCompactSkillsView )
32757 {
32758 leftWingAllSkillsImg->pos.x = 6 + leftWingImg->pos.x;
32759 leftWingAllSkillsImg->pos.y = 22 + leftWingImg->pos.y;
32760 }
32761 else
32762 {
32763 leftWingAllSkillsImg->pos.x = 6 + leftWingImg->pos.x;
32764 leftWingAllSkillsImg->pos.y = 32 + leftWingImg->pos.y;
32765 }
32766 leftWingAllSkillsImg->pos.w = imgGet->getWidth();
32767 leftWingAllSkillsImg->pos.h = imgGet->getHeight();
32768 }
32769 if ( auto imgGet = Image::get(rightWingAllSkillsImg->path.c_str()) )
32770 {
32771 if ( bUseCompactSkillsView )
32772 {
32773 rightWingAllSkillsImg->pos.x = 152 + rightWingImg->pos.x;
32774 rightWingAllSkillsImg->pos.y = 22 + rightWingImg->pos.y;
32775 }
32776 else
32777 {
32778 rightWingAllSkillsImg->pos.x = 152 + rightWingImg->pos.x;
32779 rightWingAllSkillsImg->pos.y = 32 + rightWingImg->pos.y;
32780 }
32781 rightWingAllSkillsImg->pos.w = imgGet->getWidth();
32782 rightWingAllSkillsImg->pos.h = imgGet->getHeight();
32783 }
32784 leftWingImg->pos.h = Image::get(leftWingImg->path.c_str())->getHeight();
32785 rightWingImg->pos.h = Image::get(rightWingImg->path.c_str())->getHeight();
32786 }
32787 {
32788 // dynamic main panel width/height background image adjustment
32789 SDL_Rect bgImgFramePos = bgImgFrame->getSize();
32790 int backgroundWidth = skillDescriptionFrame->getSize().w;
32791 int backgroundHeight = innerFrame->getSize().h;
32792
32793 auto flourishTop = bgImgFrame->findImage("flourish top");
32794 flourishTop->pos.x = bgImgFramePos.w / 2 - flourishTop->pos.w / 2;
32795 auto flourishBottom = bgImgFrame->findImage("flourish bottom");
32796 flourishBottom->pos.x = bgImgFramePos.w / 2 - flourishBottom->pos.w / 2;
32797 flourishBottom->pos.y = backgroundHeight - flourishBottom->pos.h;
32798 flourishTop->disabled = true;
32799 flourishBottom->disabled = true;
32800
32801
32802 bgImgFramePos.x = innerFrame->getSize().w / 2 - backgroundWidth / 2;
32803 if ( skillSlideDirection != 0 )
32804 {
32805 bgImgFramePos.x += (skillSlideAmount) * slideTravelDistance;
32806 }
32807 bgImgFramePos.y = 0;
32808 bgImgFramePos.w = backgroundWidth;
32809 bgImgFramePos.h = backgroundHeight;
32810 bgImgFrame->setSize(bgImgFramePos);
32811
32812 if ( backgroundWidth == 480 && backgroundHeight == 500 && *cvar_skillsheet_optimise )
32813 {
32814 for ( auto img : bgImgFrame->getImages() )
32815 {
32816 img->disabled = true;
32817 }
32818 auto mm = bgImgFrame->findImage(skillsheetEffectBackgroundImages[MIDDLE].c_str());
32819 mm->pos.x = 0;
32820 mm->pos.y = 0;
32821 mm->pos.w = backgroundWidth;
32822 mm->pos.h = backgroundHeight;
32823 mm->color = makeColorRGB(255, 255, 255);
32824 mm->path = ("*#images/ui/SkillSheet/UI_Skills_Full_480x500.png");
32825 mm->disabled = false;
32826 }
32827 else if ( backgroundWidth == 360 && backgroundHeight == 352 && *cvar_skillsheet_optimise )
32828 {
32829 for ( auto img : bgImgFrame->getImages() )
32830 {
32831 img->disabled = true;
32832 }
32833 auto mm = bgImgFrame->findImage(skillsheetEffectBackgroundImages[MIDDLE].c_str());
32834 mm->pos.x = 0;
32835 mm->pos.y = 0;
32836 mm->pos.w = backgroundWidth;
32837 mm->pos.h = backgroundHeight;
32838 mm->color = makeColorRGB(255, 255, 255);
32839 mm->path = ("*#images/ui/SkillSheet/UI_Skills_Full_360x352.png");
32840 mm->disabled = false;
32841 }
32842 else if ( backgroundWidth == 372 && backgroundHeight == 352 && *cvar_skillsheet_optimise )
32843 {
32844 for ( auto img : bgImgFrame->getImages() )
32845 {
32846 img->disabled = true;
32847 }
32848 auto mm = bgImgFrame->findImage(skillsheetEffectBackgroundImages[MIDDLE].c_str());
32849 mm->pos.x = 0;
32850 mm->pos.y = 0;
32851 mm->pos.w = backgroundWidth;
32852 mm->pos.h = backgroundHeight;
32853 mm->color = makeColorRGB(255, 255, 255);
32854 mm->path = ("*#images/ui/SkillSheet/UI_Skills_Full_372x352.png");
32855 mm->disabled = false;
32856 }
32857 else if ( backgroundWidth == 372 && backgroundHeight == 500 && *cvar_skillsheet_optimise )
32858 {
32859 for ( auto img : bgImgFrame->getImages() )
32860 {
32861 img->disabled = true;
32862 }
32863 auto mm = bgImgFrame->findImage(skillsheetEffectBackgroundImages[MIDDLE].c_str());
32864 mm->pos.x = 0;
32865 mm->pos.y = 0;
32866 mm->pos.w = backgroundWidth;
32867 mm->pos.h = backgroundHeight;
32868 mm->color = makeColorRGB(255, 255, 255);
32869 mm->path = ("*#images/ui/SkillSheet/UI_Skills_Full_372x500.png");
32870 mm->disabled = false;
32871 }
32872 else
32873 {
32874 for ( auto img : bgImgFrame->getImages() )
32875 {
32876 img->disabled = false;
32877 }
32878 flourishTop->disabled = false;
32879 flourishBottom->disabled = false;
32880 auto mm = bgImgFrame->findImage(skillsheetEffectBackgroundImages[MIDDLE].c_str());
32881 mm->path = "images/system/white.png";
32882 mm->color = makeColor(0, 0, 0, 255);
32883 imageResizeToContainer9x9(bgImgFrame, SDL_Rect{0, 12, backgroundWidth, backgroundHeight - 6 }, skillsheetEffectBackgroundImages);
32884 }
32885 }
32886
32887 //DebugTimers.addTimePoint("skill", "post resize");
32888
32889 sheetSize.x = skillFrame->getSize().w / 2 - sheetSize.w / 2;
32890
32891 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
32892 real_t setpointDiffX = fpsScale * std::max(.01, (1.0 - skillsFadeInAnimationY)) / 3.0;
32893 skillsFadeInAnimationY += setpointDiffX;
32894 skillsFadeInAnimationY = std::min(1.0, skillsFadeInAnimationY);
32895
32896 auto fade = skillFrame->findImage("fade img");
32897 Uint8 r, g, b, a;
32898 getColor(fade->color, &r, &g, &b, &a);
32899 a = 128 * skillsFadeInAnimationY;
32900 fade->color = makeColor( r, g, b, a);
32901 fade->disabled = false;
32902 fade->pos.w = skillFrame->getSize().w;
32903 fade->pos.h = skillFrame->getSize().h;
32904
32905 int baseY = (skillFrame->getSize().h / 2 - sheetSize.h / 2);
32906 sheetSize.y = -sheetSize.h + skillsFadeInAnimationY * (baseY + sheetSize.h);
32907 innerFrame->setOpacity(pow(skillsFadeInAnimationY, 2) * 100.0);
32908 innerFrame->setSize(sheetSize);
32909
32910 auto slider = skillDescriptionFrame->findSlider("skill slider");
32911 if ( slider->isSelected() )
32912 {
32913 slider->deselect();
32914 }
32915 //slider->setHideSelectors(false);
32916 bool sliderDisabled = slider->isDisabled();
32917
32918 auto titleText = innerFrame->findField("skill title txt");
32919 SDL_Rect titleTextPos = titleText->getSize();
32920 titleTextPos.x = skillDescriptionFrame->getSize().x + skillDescriptionFrame->getSize().w / 2 - titleTextPos.w / 2;
32921 titleText->setSize(titleTextPos);
32922
32923 if ( skillSlideDirection != 0 )
32924 {
32925 const real_t fpsScale = (getFPSScale(144.0)); // ported from 144Hz
32926 real_t setpointDiff = std::max(0.1, 1.0 - abs(skillSlideAmount));
32927 skillSlideAmount += fpsScale * (setpointDiff / 5.0) * skillSlideDirection;
32928 if ( skillSlideAmount < -1.0 )
32929 {
32930 skillSlideAmount = -1.0;
32931 }
32932 if ( skillSlideAmount > 1.0 )
32933 {
32934 skillSlideAmount = 1.0;
32935 }
32936 }
32937
32938 if ( oldSkillSlideAmount != skillSlideAmount )
32939 {
32940 reblitFrame = true;
32941 }
32942
32943 int lowestSkillEntryY = 0;
32944 bool dpad_moved = false;
32945 if ( ::inputs.getVirtualMouse(player.playernum)->draw_cursor )
32946 {
32947 highlightedSkill = -1; // if using mouse, clear out the highlighted skill data to be updated below
32948 }
32949 int defaultHighlightedSkill = 0;
32950 if ( selectedSkill >= 0 && selectedSkill < NUMPROFICIENCIES )
32951 {
32952 defaultHighlightedSkill = selectedSkill;
32953 }
32954 bool closeSheetAction = false;
32955 if ( player.GUI.activeModule == player.GUI.MODULE_SKILLS_LIST )
32956 {
32957 if ( Input::inputs[player.playernum].binaryToggle("MenuUp")
32958 && player.bControlEnabled && !gamePaused && !player.usingCommand() )
32959 {
32960 dpad_moved = true;
32961 Input::inputs[player.playernum].consumeBinaryToggle("MenuUp");
32962 if ( highlightedSkill < 0 || highlightedSkill >= NUMPROFICIENCIES )
32963 {
32964 highlightedSkill = defaultHighlightedSkill;
32965 }
32966 else if ( highlightedSkill < NUMPROFICIENCIES / 2 )
32967 {
32968 --highlightedSkill;
32969 if ( highlightedSkill < 0 )
32970 {
32971 highlightedSkill = (NUMPROFICIENCIES / 2) - 1;
32972 }
32973 }
32974 else
32975 {
32976 --highlightedSkill;
32977 if ( highlightedSkill < NUMPROFICIENCIES / 2 )
32978 {
32979 highlightedSkill = NUMPROFICIENCIES - 1;
32980 }
32981 }
32982 if ( selectedSkill != highlightedSkill )
32983 {
32984 selectSkill(highlightedSkill);
32985 }
32986 }
32987 if ( Input::inputs[player.playernum].binaryToggle("MenuDown")
32988 && player.bControlEnabled && !gamePaused && !player.usingCommand() )
32989 {
32990 dpad_moved = true;
32991 Input::inputs[player.playernum].consumeBinaryToggle("MenuDown");
32992 if ( highlightedSkill < 0 || highlightedSkill >= NUMPROFICIENCIES )
32993 {
32994 highlightedSkill = defaultHighlightedSkill;
32995 }
32996 else if ( highlightedSkill < (NUMPROFICIENCIES / 2) )
32997 {
32998 ++highlightedSkill;
32999 if ( highlightedSkill >= NUMPROFICIENCIES / 2 )
33000 {
33001 highlightedSkill = 0;
33002 }
33003 }
33004 else
33005 {
33006 ++highlightedSkill;
33007 if ( highlightedSkill >= NUMPROFICIENCIES )
33008 {
33009 highlightedSkill = (NUMPROFICIENCIES / 2);
33010 }
33011 }
33012 if ( selectedSkill != highlightedSkill )
33013 {
33014 selectSkill(highlightedSkill);
33015 }
33016 }
33017 if ( Input::inputs[player.playernum].binaryToggle("MenuLeft")
33018 && player.bControlEnabled && !gamePaused && !player.usingCommand() )
33019 {
33020 dpad_moved = true;
33021 Input::inputs[player.playernum].consumeBinaryToggle("MenuLeft");
33022 if ( highlightedSkill < 0 || highlightedSkill >= NUMPROFICIENCIES )
33023 {
33024 highlightedSkill = defaultHighlightedSkill;
33025 }
33026 else if ( highlightedSkill < NUMPROFICIENCIES / 2 )
33027 {
33028 highlightedSkill += NUMPROFICIENCIES / 2;
33029 }
33030 else
33031 {
33032 highlightedSkill -= NUMPROFICIENCIES / 2;
33033 }
33034 if ( selectedSkill != highlightedSkill )
33035 {
33036 selectSkill(highlightedSkill);
33037 }
33038 }
33039 if ( Input::inputs[player.playernum].binaryToggle("MenuRight")
33040 && player.bControlEnabled && !gamePaused && !player.usingCommand() )
33041 {
33042 dpad_moved = true;
33043 Input::inputs[player.playernum].consumeBinaryToggle("MenuRight");
33044 if ( highlightedSkill < 0 || highlightedSkill >= NUMPROFICIENCIES )
33045 {
33046 highlightedSkill = defaultHighlightedSkill;
33047 }
33048 else if ( highlightedSkill < NUMPROFICIENCIES / 2 )
33049 {
33050 highlightedSkill += NUMPROFICIENCIES / 2;
33051 }
33052 else
33053 {
33054 highlightedSkill -= NUMPROFICIENCIES / 2;
33055 }
33056 if ( selectedSkill != highlightedSkill )
33057 {
33058 selectSkill(highlightedSkill);
33059 }
33060 }
33061
33062 if ( dpad_moved )
33063 {
33064 if ( skillSlideDirection != 0 )
33065 {
33066 if ( highlightedSkill >= 8 )
33067 {
33068 skillSlideDirection = 1;
33069 }
33070 else
33071 {
33072 skillSlideDirection = -1;
33073 }
33074 }
33075 inputs.getVirtualMouse(player.playernum)->draw_cursor = false;
33076 Player::soundMovement();
33077 }
33078 if ( Input::inputs[player.playernum].binaryToggle("MenuCancel")
33079 && player.bControlEnabled && !gamePaused && !player.usingCommand() )
33080 {
33081 Input::inputs[player.playernum].consumeBinaryToggle("MenuCancel");
33082 closeSheetAction = true;
33083 }
33084 }
33085 bool mouseClickedOutOfBounds = false;
33086 if ( inputs.bPlayerUsingKeyboardControl(player.playernum) && Input::inputs[player.playernum].binaryToggle("MenuLeftClick") )
33087 {
33088 mouseClickedOutOfBounds = true;
33089 }
33090
33091 //DebugTimers.addTimePoint("skill", "post inputs");
33092
33093 if ( stats[player.playernum] && skillSheetData.skillEntries.size() > 0 )
33094 {
33095 bool skillDescAreaCapturesMouse = bgImgFrame->capturesMouse();
33096 if ( skillDescAreaCapturesMouse ) { mouseClickedOutOfBounds = false; }
33097 const int skillEntryStartY = bUseCompactSkillsView ? 28 : 38;
33098 const int entryHeight = bUseCompactSkillsView ? 36 : 40;
33099 SDL_Rect entryResizePos{ 0, skillEntryStartY, 0, entryHeight };
33100 bool capturedMouseOnEntry = false;
33101 for ( int i = 0; i < NUMPROFICIENCIES; ++i )
33102 {
33103 Frame* allSkillEntries = allSkillEntriesRight;
33104 if ( i >= 8 )
33105 {
33106 allSkillEntries = allSkillEntriesLeft;
33107 if ( i == 8 )
33108 {
33109 entryResizePos.y = skillEntryStartY;
33110 }
33111 }
33112 auto entry = skillSheetEntryFrames[player.playernum].entryFrames[i];
33113
33114 if ( i >= skillSheetData.skillEntries.size() )
33115 {
33116 entry->setDisabled(true);
33117 break;
33118 }
33119 entry->setDisabled(false);
33120 SDL_Rect entryPos = entry->getSize();
33121 entryPos.y = entryResizePos.y;
33122 entryPos.h = entryResizePos.h;
33123 entry->setSize(entryPos);
33124 entryResizePos.y += entryResizePos.h;
33125 lowestSkillEntryY = std::max(lowestSkillEntryY, entryPos.y + entryPos.h);
33126
33127 int proficiency = skillSheetData.skillEntries[i].skillId;
33128 bool updateSkillAssets = false;
33129 if ( !capturedMouseOnEntry )
33130 {
33131 const bool entryCapturesMouse = entry->capturesMouse();
33132 if ( entryCapturesMouse )
33133 {
33134 capturedMouseOnEntry = true;
33135 mouseClickedOutOfBounds = false;
33136 if ( ::inputs.getVirtualMouse(player.playernum)->draw_cursor
33137 && entryCapturesMouse && !skillDescAreaCapturesMouse )
33138 {
33139 highlightedSkill = i;
33140 if ( skillSlideDirection != 0 )
33141 {
33142 if ( highlightedSkill >= 8 )
33143 {
33144 skillSlideDirection = 1;
33145 }
33146 else
33147 {
33148 skillSlideDirection = -1;
33149 }
33150 }
33151 if ( Input::inputs[player.playernum].binaryToggle("MenuLeftClick") && inputs.bPlayerUsingKeyboardControl(player.playernum) )
33152 {
33153 selectSkill(i);
33154 Input::inputs[player.playernum].consumeBinaryToggle("MenuLeftClick");
33155 Player::soundActivate();
33156 }
33157 }
33158 }
33159 }
33160
33161 if ( !bSkillSheetEntryLoaded
33162 || skillsheetCache[player.playernum][proficiency].proficiencyLevelCached != stats[player.playernum]->getModifiedProficiency(proficiency)
33163 || skillsheetCache[player.playernum][proficiency].selectedSkill != selectedSkill
33164 || skillsheetCache[player.playernum][proficiency].highlightedSkill != highlightedSkill )
33165 {
33166 if ( skillsheetCache[player.playernum][proficiency].proficiencyLevelCached != stats[player.playernum]->getModifiedProficiency(proficiency)
33167 || skillsheetCache[player.playernum][proficiency].selectedSkill != selectedSkill )
33168 {
33169 reblitFrame = true;
33170 }
33171 skillsheetCache[player.playernum][proficiency].proficiencyLevelCached = stats[player.playernum]->getModifiedProficiency(proficiency);
33172 skillsheetCache[player.playernum][proficiency].selectedSkill = selectedSkill;
33173 skillsheetCache[player.playernum][proficiency].highlightedSkill = highlightedSkill;
33174 updateSkillAssets = true;
33175 }
33176
33177 if ( updateSkillAssets )
33178 {
33179 auto skillLevel = entry->findField("skill level");
33180 char skillLevelText[32];
33181 snprintf(skillLevelText, sizeof(skillLevelText), "%d", stats[player.playernum]->getModifiedProficiency(proficiency));
33182 skillLevel->setText(skillLevelText);
33183
33184 auto skillIconBg = entry->findImage("skill icon bg");
33185 auto skillIconFg = entry->findImage("skill icon fg");
33186 //skillIconFg->path = skillSheetData.skillEntries[i].skillIconPath;
33187 skillIconFg->disabled = true;
33188 auto statIcon = entry->findImage("stat icon");
33189 statIcon->disabled = true;
33190
33191 auto selectorIcon = entry->findImage("selector img");
33192 selectorIcon->disabled = true;
33193 skillIconBg->disabled = false;
33194
33195 if ( selectedSkill == i || highlightedSkill == i )
33196 {
33197 selectorIcon->disabled = false;
33198 if ( selectedSkill == i && highlightedSkill == i )
33199 {
33200 selectorIcon->path = (i < 8) ? "*#images/ui/SkillSheet/UI_Skills_SkillSelector100_01.png" : "*#images/ui/SkillSheet/UI_Skills_SkillSelector100R_01.png";
33201 }
33202 else if ( highlightedSkill == i )
33203 {
33204 selectorIcon->path = (i < 8) ? skillSheetData.highlightSkillImg : skillSheetData.highlightSkillImg_Right;
33205 }
33206 else if ( selectedSkill == i )
33207 {
33208 selectorIcon->path = (i < 8) ? skillSheetData.selectSkillImg : skillSheetData.selectSkillImg_Right;
33209 }
33210 }
33211
33212 if ( stats[player.playernum]->getModifiedProficiency(proficiency) >= SKILL_LEVEL_LEGENDARY )
33213 {
33214 //skillIconFg->path = skillSheetData.skillEntries[i].skillIconPathLegend;
33215 skillLevel->setTextColor(skillSheetData.legendTextColor);
33216 if ( selectedSkill == i )
33217 {
33218 skillIconBg->path = skillSheetData.iconBgSelectedPathLegend;
33219 }
33220 else
33221 {
33222 skillIconBg->path = skillSheetData.iconBgPathLegend;
33223 }
33224 }
33225 else if ( stats[player.playernum]->getModifiedProficiency(proficiency) >= SKILL_LEVEL_EXPERT )
33226 {
33227 skillLevel->setTextColor(skillSheetData.expertTextColor);
33228 if ( selectedSkill == i )
33229 {
33230 skillIconBg->path = skillSheetData.iconBgSelectedPathExpert;
33231 }
33232 else
33233 {
33234 skillIconBg->path = skillSheetData.iconBgPathExpert;
33235 }
33236 }
33237 else if ( stats[player.playernum]->getModifiedProficiency(proficiency) >= SKILL_LEVEL_BASIC )
33238 {
33239 skillLevel->setTextColor(skillSheetData.noviceTextColor);
33240 if ( selectedSkill == i )
33241 {
33242 skillIconBg->path = skillSheetData.iconBgSelectedPathNovice;
33243 }
33244 else
33245 {
33246 skillIconBg->path = skillSheetData.iconBgPathNovice;
33247 }
33248 }
33249 else
33250 {
33251 skillLevel->setTextColor(skillSheetData.defaultTextColor);
33252 if ( selectedSkill == i )
33253 {
33254 skillIconBg->path = skillSheetData.iconBgSelectedPathDefault;
33255 }
33256 else
33257 {
33258 skillIconBg->path = skillSheetData.iconBgPathDefault;
33259 skillIconBg->disabled = true;
33260 }
33261 }
33262 }
33263 //statIcon->path = skillIconFg->path;
33264 //skillIconFg->path = "";
33265 }
33266
33267 //DebugTimers.addTimePoint("skill", "post basic numbers");
33268
33269 int lowestY = 0;
33270
33271 SDL_Rect sliderPos = slider->getRailSize();
33272 sliderPos.x = skillDescriptionFrame->getSize().w - 34;
33273 sliderPos.h = skillDescriptionFrame->getSize().h - 8;
33274 slider->setRailSize(sliderPos);
33275
33276 int sliderOffsetW = 0;
33277 if ( slider->isDisabled() )
33278 {
33279 int diff = (scrollOuterFramePos.w - (skillDescriptionFrame->getSize().w - 32));
33280 sliderOffsetW = -diff;
33281 }
33282 else
33283 {
33284 int diff = (scrollOuterFramePos.w - (slider->getRailSize().x - 4 - 16));
33285 sliderOffsetW = -diff;
33286 }
33287 if ( sliderOffsetW != 0 )
33288 {
33289 bSkillSheetEntryLoaded = false;
33290 }
33291
33292 scrollOuterFramePos.w += sliderOffsetW;
33293 scrollAreaOuterFrame->setSize(scrollOuterFramePos);
33294 scrollAreaPos.w = scrollOuterFramePos.w;
33295 scrollArea->setSize(scrollAreaPos);
33296
33297 if ( slider->isDisabled() )
33298 {
33299 scrollInertia = 0.0;
33300 }
33301
33302 //DebugTimers.addTimePoint("skill", "post resize");
33303
33304 if ( selectedSkill >= 0 && selectedSkill < skillSheetData.skillEntries.size() )
33305 {
33306 int proficiency = skillSheetData.skillEntries[selectedSkill].skillId;
33307 int proficiencyValue = stats[player.playernum]->getModifiedProficiency(proficiency);
33308
33309 bool updateTitle = false;
33310 for ( auto& eff_t : skillSheetData.skillEntries[selectedSkill].effects )
33311 {
33312 if ( eff_t.effectUpdatedAtSkillLevel != proficiencyValue
33313 || eff_t.effectUpdatedAtBaseSkillLevel != stats[player.playernum]->getProficiency(proficiency) )
33314 {
33315 updateTitle = true;
33316 break;
33317 }
33318 }
33319
33320 if ( !bSkillSheetEntryLoaded || updateTitle )
33321 {
33322 auto skillLvlHeaderVal = scrollArea->findField("skill lvl header val");
33323 char skillLvl[128] = "";
33324 std::string skillLvlTitle = "";
33325 if ( proficiencyValue >= SKILL_LEVEL_LEGENDARY )
33326 {
33327 skillLvlTitle = Language::get(369);
33328 }
33329 else if ( proficiencyValue >= SKILL_LEVEL_MASTER )
33330 {
33331 skillLvlTitle = Language::get(368);
33332 }
33333 else if ( proficiencyValue >= SKILL_LEVEL_EXPERT )
33334 {
33335 skillLvlTitle = Language::get(367);
33336 }
33337 else if ( proficiencyValue >= SKILL_LEVEL_SKILLED )
33338 {
33339 skillLvlTitle = Language::get(366);
33340 }
33341 else if ( proficiencyValue >= SKILL_LEVEL_BASIC )
33342 {
33343 skillLvlTitle = Language::get(365);
33344 }
33345 else if ( proficiencyValue >= SKILL_LEVEL_NOVICE )
33346 {
33347 skillLvlTitle = Language::get(364);
33348 }
33349 else
33350 {
33351 skillLvlTitle = Language::get(363);
33352 }
33353 skillLvlTitle.erase(std::remove(skillLvlTitle.begin(), skillLvlTitle.end(), ' '), skillLvlTitle.end()); // trim whitespace
33354 int effectsModifier = stats[player.playernum]->getModifiedProficiency(proficiency) - stats[player.playernum]->getProficiency(proficiency);
33355 if ( proficiencyValue > 100 )
33356 {
33357 effectsModifier = std::max(effectsModifier - (proficiencyValue - 100), 0);
33358 }
33359 if ( effectsModifier > 0 )
33360 {
33361 snprintf(skillLvl, sizeof(skillLvl), "%s (%d + %d)", skillLvlTitle.c_str(),
33362 stats[player.playernum]->getProficiency(proficiency), effectsModifier);
33363 }
33364 else if ( effectsModifier < 0 )
33365 {
33366 snprintf(skillLvl, sizeof(skillLvl), "%s (%d - %d)", skillLvlTitle.c_str(),
33367 stats[player.playernum]->getProficiency(proficiency), abs(effectsModifier));
33368 }
33369 else
33370 {
33371 snprintf(skillLvl, sizeof(skillLvl), "%s (%d)", skillLvlTitle.c_str(), stats[player.playernum]->getModifiedProficiency(proficiency));
33372 }
33373 skillLvlHeaderVal->setText(skillLvl);
33374
33375 SDL_Rect skillLvlHeaderValPos = skillLvlHeaderVal->getSize();
33376 skillLvlHeaderValPos.x += sliderOffsetW;
33377 skillLvlHeaderVal->setSize(skillLvlHeaderValPos);
33378
33379 if ( !bSkillSheetEntryLoaded )
33380 {
33381 auto skillTitleTxt = innerFrame->findField("skill title txt");
33382 skillTitleTxt->setText(skillSheetData.skillEntries[selectedSkill].name.c_str());
33383
33384 auto statTypeTxt = scrollArea->findField("stat type txt");
33385 auto statIcon = scrollArea->findImage("stat icon");
33386 statIcon->path = skillSheetData.skillEntries[selectedSkill].statIconPath;
33387 switch ( getStatForProficiency(proficiency) )
33388 {
33389 case STAT_STR:
33390 statTypeTxt->setText(Language::get(5300));
33391 break;
33392 case STAT_DEX:
33393 statTypeTxt->setText(Language::get(5301));
33394 break;
33395 case STAT_CON:
33396 statTypeTxt->setText(Language::get(5302));
33397 break;
33398 case STAT_INT:
33399 statTypeTxt->setText(Language::get(5303));
33400 break;
33401 case STAT_PER:
33402 statTypeTxt->setText(Language::get(5304));
33403 break;
33404 case STAT_CHR:
33405 statTypeTxt->setText(Language::get(5305));
33406 break;
33407 default:
33408 break;
33409 }
33410
33411 SDL_Rect statTypeTxtPos = statTypeTxt->getSize();
33412 statTypeTxtPos.x += sliderOffsetW;
33413 statTypeTxt->setSize(statTypeTxtPos);
33414 statIcon->pos.x += sliderOffsetW;
33415 }
33416 }
33417
33418 //DebugTimers.addTimePoint("skill", "process 1");
33419
33420 int moveEffectsOffsetY = 0;
33421 const int actualFontHeight = 20;
33422 if ( !bSkillSheetEntryLoaded )
33423 {
33424 auto skillDescriptionTxt = scrollArea->findField("skill desc txt");
33425 auto skillDescriptionBgFrame = scrollArea->findFrame("skill desc bg frame");
33426 //Font* actualFont = Font::get(skillDescriptionTxt->getFont());
33427 SDL_Rect skillDescriptionTxtPos = skillDescriptionTxt->getSize();
33428 skillDescriptionTxtPos.w = scrollAreaPos.w;
33429 skillDescriptionTxt->setSize(skillDescriptionTxtPos);
33430
33431 int txtHeightOld = skillDescriptionTxt->getNumTextLines() * actualFontHeight/*actualFont->height(true)*/;
33432 skillDescriptionTxt->setText(skillSheetData.skillEntries[selectedSkill].description.c_str());
33433 skillDescriptionTxt->reflowTextToFit(0);
33434 int txtHeightNew = skillDescriptionTxt->getNumTextLines() * actualFontHeight/*actualFont->height(true)*/;
33435
33436 if ( txtHeightNew != txtHeightOld )
33437 {
33438 moveEffectsOffsetY = (txtHeightNew - txtHeightOld);
33439 }
33440 skillDescriptionBgFrame->setSize(SDL_Rect{skillDescriptionTxtPos.x, skillDescriptionTxtPos.y - 4,
33441 skillDescriptionTxtPos.w, txtHeightNew + 8 });
33442 lowestY = std::max(lowestY, skillDescriptionTxt->getSize().y + skillDescriptionTxt->getNumTextLines() * actualFontHeight/*actualFont->height(true)*/);
33443
33444 std::string staticImgPath = "*#images/ui/SkillSheet/UI_Skills_Desc_Full_";
33445 staticImgPath += std::to_string(skillDescriptionBgFrame->getSize().w) + 'x' + std::to_string(skillDescriptionBgFrame->getSize().h) + ".png";
33446
33447 auto staticImg = Image::get(staticImgPath.c_str());
33448 if ( staticImg && staticImg->getWidth() > 0 && *cvar_skillsheet_optimise )
33449 {
33450 for ( auto img : skillDescriptionBgFrame->getImages() )
33451 {
33452 img->disabled = true;
33453 }
33454 auto mm = skillDescriptionBgFrame->findImage(skillsheetEffectBackgroundImages[MIDDLE].c_str());
33455 mm->disabled = false;
33456 mm->path = staticImgPath;
33457 mm->pos = SDL_Rect{ 0, 0, skillDescriptionBgFrame->getSize().w, skillDescriptionBgFrame->getSize().h };
33458 }
33459 else
33460 {
33461 for ( auto img : skillDescriptionBgFrame->getImages() )
33462 {
33463 img->disabled = false;
33464 }
33465 auto mm = skillDescriptionBgFrame->findImage(skillsheetEffectBackgroundImages[MIDDLE].c_str());
33466 mm->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox_M_00.png";
33467 imageResizeToContainer9x9(skillDescriptionBgFrame,
33468 SDL_Rect{ 0, 0, skillDescriptionBgFrame->getSize().w, skillDescriptionBgFrame->getSize().h }, skillsheetEffectBackgroundImages);
33469 }
33470 }
33471
33472 bool skillLVLUpdated = false;
33473 int previousEffectFrameHeight = 0;
33474
33475 auto effectFrameBgImgTmp = scrollArea->findImage("effect frame bg tmp");
33476 effectFrameBgImgTmp->disabled = false;
33477
33478 for ( int eff = 0; eff < 10; ++eff )
33479 {
33480 auto effectFrame = skillSheetEntryFrames[player.playernum].effectFrames[eff];
33481 if ( !effectFrame ) { continue; }
33482
33483 effectFrame->setDisabled(true);
33484
33485 if ( eff < skillSheetData.skillEntries[selectedSkill].effects.size() )
33486 {
33487 SDL_Rect effectFramePos = effectFrame->getSize();
33488 effectFramePos.w = scrollAreaPos.w;
33489 effectFrameBgImgTmp->pos.w = effectFramePos.w;
33490
33491 if ( moveEffectsOffsetY != 0 )
33492 {
33493 effectFramePos.y += moveEffectsOffsetY;
33494 }
33495 effectFrame->setSize(effectFramePos);
33496
33497 effectFrame->setDisabled(false);
33498
33499 auto& effect_t = skillSheetData.skillEntries[selectedSkill].effects[eff];
33500
33501 bool bEffUpdated = false;
33502 if ( (effect_t.bAllowRealtimeUpdate && (ticks % (std::max(TICKS_PER_SECOND50, MAXPLAYERS4 * 10))) == (player.playernum * 10))
33503 || effect_t.effectUpdatedAtSkillLevel != stats[player.playernum]->getModifiedProficiency(proficiency)
33504 || effect_t.effectUpdatedAtBaseSkillLevel != stats[player.playernum]->getProficiency(proficiency)
33505 || effect_t.effectUpdatedAtMonsterType != stats[player.playernum]->type
33506 || effect_t.value == "" )
33507 {
33508 if ( effect_t.effectUpdatedAtSkillLevel != stats[player.playernum]->getModifiedProficiency(proficiency)
33509 || effect_t.effectUpdatedAtBaseSkillLevel != stats[player.playernum]->getProficiency(proficiency) )
33510 {
33511 skillLVLUpdated = true;
33512 bEffUpdated = true;
33513 }
33514 else if ( effect_t.effectUpdatedAtMonsterType != stats[player.playernum]->type )
33515 {
33516 bEffUpdated = true;
33517 }
33518 effect_t.effectUpdatedAtSkillLevel = stats[player.playernum]->getModifiedProficiency(proficiency);
33519 effect_t.effectUpdatedAtBaseSkillLevel = stats[player.playernum]->getProficiency(proficiency);
33520 effect_t.effectUpdatedAtMonsterType = stats[player.playernum]->type;
33521 std::string oldValue = effect_t.value;
33522 effect_t.value = formatSkillSheetEffects(player.playernum, proficiency, effect_t.tag, effect_t.rawValue);
33523 if ( oldValue != effect_t.value )
33524 {
33525 bEffUpdated = true;
33526 }
33527 auto effectValFrame = effectFrame->findFrame("effect val frame");
33528 auto effectVal = effectValFrame->findField("effect val");
33529 effectVal->setText(effect_t.value.c_str());
33530 if ( effect_t.bAllowAutoResizeValue )
33531 {
33532 auto textGetValue = Text::get(effectVal->getLongestLine().c_str(), effectVal->getFont(),
33533 effectVal->getTextColor(), effectVal->getOutlineColor());
33534 effect_t.cachedWidth = textGetValue->getWidth();
33535 }
33536 }
33537
33538 if ( !bSkillSheetEntryLoaded || bEffUpdated )
33539 {
33540 reblitFrame = true;
33541
33542 auto effectValFrame = effectFrame->findFrame("effect val frame");
33543 auto effectVal = effectValFrame->findField("effect val");
33544 auto effectTxtFrame = effectFrame->findFrame("effect txt frame");
33545 auto effectTxt = effectTxtFrame->findField("effect txt");
33546 auto effectBgImgFrame = effectFrame->findFrame("effect val bg frame");
33547 effectTxt->setText(effect_t.title.c_str());
33548
33549 {
33550 // adjust position to match width of container
33551 SDL_Rect effectTxtFramePos = effectTxtFrame->getSize();
33552 SDL_Rect effectValFramePos = effectValFrame->getSize();
33553 SDL_Rect effectBgImgFramePos = effectBgImgFrame->getSize();
33554 const auto& effectStartOffsetX = skillSheetData.skillEntries[selectedSkill].effectStartOffsetX;
33555 const auto& effectBackgroundOffsetX = skillSheetData.skillEntries[selectedSkill].effectBackgroundOffsetX;
33556 const auto& effectBackgroundWidth = skillSheetData.skillEntries[selectedSkill].effectBackgroundWidth;
33557 int valueCustomWidthOffset = effect_t.valueCustomWidthOffset;
33558 effectBgImgFramePos.x = effectFrame->getSize().w - effectStartOffsetX - effectBackgroundOffsetX - valueCustomWidthOffset;
33559 effectBgImgFramePos.w = effectBackgroundWidth + valueCustomWidthOffset;
33560 effectTxtFramePos.w = effectFrame->getSize().w - effectStartOffsetX - effectBackgroundOffsetX - valueCustomWidthOffset;
33561 effectValFramePos.x = effectFrame->getSize().w - effectStartOffsetX - valueCustomWidthOffset;
33562 effectValFramePos.w = effectStartOffsetX + valueCustomWidthOffset;
33563 if ( effect_t.bAllowAutoResizeValue
33564 && effect_t.cachedWidth > (effectValFramePos.w - effectVal->getSize().x - effectBackgroundOffsetX) )
33565 {
33566 int diff = (effect_t.cachedWidth - (effectValFramePos.w - effectVal->getSize().x - effectBackgroundOffsetX));
33567 effectBgImgFramePos.x -= diff;
33568 effectBgImgFramePos.w += diff;
33569 effectTxtFramePos.w -= diff;
33570 effectValFramePos.x -= diff;
33571 effectValFramePos.w += diff;
33572 }
33573
33574 effectTxtFrame->setSize(effectTxtFramePos);
33575 effectValFrame->setSize(effectValFramePos);
33576 effectBgImgFrame->setSize(effectBgImgFramePos);
33577 }
33578
33579 effectFramePos = effectFrame->getSize();
33580
33581 //Font* effectTxtFont = Font::get(effectTxt->getFont());
33582 const int fontHeight = 20;
33583 //effectTxtFont->sizeText("_", nullptr, &fontHeight);
33584 int numEffectLines = effectTxt->getNumTextLines();
33585 int numEffectValLines = effectVal->getNumTextLines();
33586 int numEffectValBgLines = numEffectValLines;
33587 if ( numEffectLines > 1 || numEffectValLines > 1 )
33588 {
33589 if ( numEffectValLines <= 1 )
33590 {
33591 // single line value, only need lines of title
33592 effectFramePos.h = (fontHeight * std::max(1, numEffectLines)) + 8;
33593 }
33594 else
33595 {
33596 if ( numEffectLines <= numEffectValLines )
33597 {
33598 numEffectValLines += 1; // need more buffer area for the values as it is larger than title
33599 effectFramePos.h = (fontHeight * numEffectValLines) + 8;
33600 }
33601 else
33602 {
33603 effectFramePos.h = (fontHeight * numEffectLines) + 8;
33604 }
33605 }
33606 }
33607 else
33608 {
33609 // both title and value are 1 line, add .5 padding
33610 effectFramePos.h = (fontHeight) * 1.5 + 8;
33611 }
33612 if ( eff > 0 )
33613 {
33614 effectFramePos.y = previousEffectFrameHeight; // don't adjust first effect frame y pos
33615 }
33616 else
33617 {
33618 effectFrameBgImgTmp->pos.y = effectFramePos.y;
33619 }
33620 effectFrame->setSize(effectFramePos);
33621
33622 {
33623 // adjust position to match height of container
33624 SDL_Rect effectTxtFramePos = effectTxtFrame->getSize();
33625 SDL_Rect effectValFramePos = effectValFrame->getSize();
33626 SDL_Rect effectBgImgFramePos = effectBgImgFrame->getSize();
33627 const int containerHeight = effectFramePos.h - 4;
33628 effectTxtFramePos.h = containerHeight;
33629 effectValFramePos.h = containerHeight;
33630 effectTxtFrame->setSize(effectTxtFramePos);
33631 effectValFrame->setSize(effectValFramePos);
33632
33633 SDL_Rect effectTxtPos = effectTxt->getSize();
33634 effectTxtPos.h = containerHeight;
33635 effectTxt->setSize(effectTxtPos);
33636 SDL_Rect effectValPos = effectVal->getSize();
33637 effectValPos.h = containerHeight;
33638 effectVal->setSize(effectValPos);
33639
33640 effectBgImgFramePos.h = (fontHeight * numEffectValBgLines) + 8;
33641 effectBgImgFramePos.y = (containerHeight / 2 - effectBgImgFramePos.h / 2);
33642 effectBgImgFrame->setSize(effectBgImgFramePos);
33643
33644 auto effectFrameBgImg = effectFrame->findImage("effect frame bg highlight");
33645 effectFrameBgImg->pos = SDL_Rect{ 0, effectFrame->getSize().h - 2, effectFrame->getSize().w, 1 };
33646 }
33647
33648 {
33649 // adjust inner background image elements
33650
33651 std::string staticImgPath = "*#images/ui/SkillSheet/UI_Skills_Eff_Full_";
33652 staticImgPath += std::to_string(effectBgImgFrame->getSize().w) + 'x' + std::to_string(effectBgImgFrame->getSize().h) + ".png";
33653
33654 auto staticImg = Image::get(staticImgPath.c_str());
33655 if ( staticImg && staticImg->getWidth() > 0 && *cvar_skillsheet_optimise )
33656 {
33657 for ( auto img : effectBgImgFrame->getImages() )
33658 {
33659 img->disabled = true;
33660 }
33661 auto mm = effectBgImgFrame->findImage(skillsheetEffectBackgroundImages[MIDDLE].c_str());
33662 mm->disabled = false;
33663 mm->path = staticImgPath;
33664 mm->color = makeColorRGB(255, 255, 255);
33665 mm->pos = SDL_Rect{ 0, 0, effectBgImgFrame->getSize().w, effectBgImgFrame->getSize().h };
33666 }
33667 else
33668 {
33669 for ( auto img : effectBgImgFrame->getImages() )
33670 {
33671 img->disabled = false;
33672 }
33673 auto mm = effectBgImgFrame->findImage(skillsheetEffectBackgroundImages[MIDDLE].c_str());
33674 mm->path = "*#images/ui/SkillSheet/UI_Skills_EffectBG_M00.png";
33675 mm->color = makeColor(51, 33, 26, 255);
33676 imageResizeToContainer9x9(effectBgImgFrame,
33677 SDL_Rect{ 0, 0, effectBgImgFrame->getSize().w, effectBgImgFrame->getSize().h }, skillsheetEffectBackgroundImages);
33678 /*static std::map<int, std::pair<int, int>> sizes;
33679 sizes[effectBgImgFrame->getSize().w + effectBgImgFrame->getSize().h * 1000] = std::make_pair(effectBgImgFrame->getSize().w, effectBgImgFrame->getSize().h);
33680 printlog("sizes");
33681 for ( auto & pair : sizes )
33682 {
33683 printlog("%d | %d", pair.second.first, pair.second.second);
33684 }*/
33685 }
33686
33687
33688 }
33689 }
33690
33691 lowestY = std::max(lowestY, effectFrame->getSize().y + effectFrame->getSize().h);
33692 effectFrameBgImgTmp->pos.h = lowestY - effectFrameBgImgTmp->pos.y;
33693 // check marquee if needed
33694 //if ( false )
33695 //{
33696 // auto textGetTitle = Text::get(effectTxt->getText(), effectTxt->getFont(),
33697 // effectTxt->getTextColor(), effectTxt->getOutlineColor());
33698 // int titleWidth = textGetTitle->getWidth();
33699 // if ( numEffectLines > 1 )
33700 // {
33701 // auto textGetTitle = Text::get(effectTxt->getLongestLine().c_str(), effectTxt->getFont(),
33702 // effectTxt->getTextColor(), effectTxt->getOutlineColor());
33703 // titleWidth = textGetTitle->getWidth();
33704 // }
33705
33706 // if ( ticks - openTick > TICKS_PER_SECOND * 2 )
33707 // {
33708 // bool doMarquee = false;
33709 // doMarquee = doMarquee || (titleWidth > (effectTxt->getSize().x + effectTxtFrame->getSize().w));
33710 // doMarquee = doMarquee || (valueWidth > (effectVal->getSize().x + effectValFrame->getSize().w));
33711
33712 // if ( doMarquee )
33713 // {
33714 // const real_t fpsScale = getFPSScale(60.0); // ported from 60Hz
33715 // effect_t.marquee[player.playernum] += (.005 * fpsScale);
33716 // //effect_t.marquee[player.playernum] = std::min(1.0, effect_t.marquee[player.playernum]);
33717
33718 // /*if ( effect_t.marqueeTicks[player.playernum] == 0 && effect_t.marquee[player.playernum] >= 1.0 )
33719 // {
33720 // effect_t.marqueeTicks[player.playernum] = ticks;
33721 // }*/
33722 // /*if ( effect_t.marqueeTicks[player.playernum] > 0 && (ticks - effect_t.marqueeTicks[player.playernum] > TICKS_PER_SECOND * 2) )
33723 // {
33724 // effect_t.marqueeTicks[player.playernum] = 0;
33725 // effect_t.marquee[player.playernum] = 0.0;
33726 // }*/
33727 // }
33728 // }
33729 // SDL_Rect posTitle = effectTxt->getSize();
33730 // int scrollTitleLength = titleWidth - effectTxtFrame->getSize().w;
33731 // if ( titleWidth <= effectTxtFrame->getSize().w )
33732 // {
33733 // scrollTitleLength = 0;
33734 // posTitle.x = 0;
33735 // effect_t.marqueeCompleted[player.playernum] = false;
33736 // effect_t.marquee[player.playernum] = 0.0;
33737 // effect_t.marqueeTicks[player.playernum] = 0;
33738 // }
33739 // else
33740 // {
33741 // posTitle.x = std::max((int)(-effect_t.marquee[player.playernum] * 100), -scrollTitleLength);
33742 // if ( posTitle.x == -scrollTitleLength )
33743 // {
33744 // if ( !effect_t.marqueeCompleted[player.playernum] )
33745 // {
33746 // effect_t.marqueeTicks[player.playernum] = ticks;
33747 // }
33748 // effect_t.marqueeCompleted[player.playernum] = true;
33749 // }
33750 // else
33751 // {
33752 // effect_t.marqueeCompleted[player.playernum] = false;
33753 // }
33754 // }
33755 // //posTitle.x = -scrollTitleLength * effect_t.marquee[player.playernum];
33756 // effectTxt->setSize(posTitle);
33757
33758 // SDL_Rect posValue = effectVal->getSize();
33759 // int scrollValueLength = valueWidth - effectValFrame->getSize().w;
33760 // if ( valueWidth <= effectValFrame->getSize().w )
33761 // {
33762 // scrollValueLength = 0;
33763 // }
33764 // posValue.x = -scrollValueLength * effect_t.marquee[player.playernum];
33765 // effectVal->setSize(posValue);
33766 //}
33767 previousEffectFrameHeight = effectFrame->getSize().y + effectFrame->getSize().h;
33768 //DebugTimers.addTimePoint("skill", effectFrame->getName());
33769 }
33770 }
33771
33772 //DebugTimers.addTimePoint("skill", "process fx 2");
33773
33774 if ( false )
33775 {
33776 Uint32 lastMarqueeTick = 0;
33777 bool allMarqueeCompleted = true;
33778 for ( auto& effect_t : skillSheetData.skillEntries[selectedSkill].effects )
33779 {
33780 if ( effect_t.marquee[player.playernum] > 0.0 )
33781 {
33782 if ( !effect_t.marqueeCompleted[player.playernum] )
33783 {
33784 allMarqueeCompleted = false;
33785 }
33786 lastMarqueeTick = std::max(effect_t.marqueeTicks[player.playernum], lastMarqueeTick);
33787 }
33788 }
33789 if ( allMarqueeCompleted && lastMarqueeTick > 0 && ((ticks - lastMarqueeTick) > 2 * TICKS_PER_SECOND50) )
33790 {
33791 for ( auto& effect_t : skillSheetData.skillEntries[selectedSkill].effects )
33792 {
33793 effect_t.marquee[player.playernum] = 0.0;
33794 effect_t.marqueeCompleted[player.playernum] = false;
33795 effect_t.marqueeTicks[player.playernum] = 0;
33796 }
33797 openTick = (ticks > TICKS_PER_SECOND50) ? (ticks - TICKS_PER_SECOND50) : ticks;
33798 }
33799 }
33800
33801 // legend panel
33802 auto legendDivImg = scrollArea->findImage("legend div");
33803 legendDivImg->pos.x = scrollArea->getSize().w / 2 - legendDivImg->pos.w / 2;
33804 legendDivImg->pos.y = lowestY + 8;
33805 auto legendDivTxt = scrollArea->findField("legend div text");
33806 SDL_Rect legendDivTxtPos = legendDivTxt->getSize();
33807 legendDivTxtPos.x = legendDivImg->pos.x;
33808 legendDivTxtPos.w = legendDivImg->pos.w;
33809 legendDivTxtPos.y = legendDivImg->pos.y + legendDivImg->pos.h - 16;
33810 legendDivTxtPos.h = actualFontHeight/*actualFont->height(true)*/;
33811 legendDivTxt->setSize(legendDivTxtPos);
33812 lowestY = legendDivTxtPos.y + legendDivTxtPos.h;
33813
33814 auto legendBackerImg = scrollArea->findImage("legend txt backer img");
33815 legendBackerImg->disabled = true;
33816 if ( proficiencyValue >= SKILL_LEVEL_LEGENDARY )
33817 {
33818 legendBackerImg->pos.x = scrollArea->getSize().w / 2 - legendBackerImg->pos.w / 2;
33819 legendBackerImg->pos.y = legendDivTxtPos.y + 6;
33820 legendBackerImg->disabled = false;
33821 }
33822
33823 auto legendFrame = skillSheetEntryFrames[player.playernum].legendFrame;
33824 SDL_Rect legendPos = legendFrame->getSize();
33825 legendPos.y = lowestY;
33826 legendPos.w = scrollArea->getSize().w;
33827 legendFrame->setSize(legendPos);
33828
33829 auto tl = legendFrame->findImage("top left img");
33830 auto tm = legendFrame->findImage("top img");
33831 auto tr = legendFrame->findImage("top right img");
33832 tm->pos.w = legendPos.w - tl->pos.w - tr->pos.w;
33833 tr->pos.x = legendPos.w - tr->pos.w;
33834
33835 if ( !bSkillSheetEntryLoaded || skillLVLUpdated )
33836 {
33837 reblitFrame = true;
33838 Uint32 legendGoldColor = makeColor(230, 183, 20, 255);
33839 Uint32 legendRegularColor = makeColor(201, 162, 100, 255);
33840
33841 auto legendText = legendFrame->findField("legend text");
33842 SDL_Rect legendTextPos = legendText->getSize();
33843 legendText->setText(skillSheetData.skillEntries[selectedSkill].legendaryDescription.c_str());
33844 legendTextPos.w = tm->pos.w;
33845 legendText->setSize(legendTextPos);
33846 legendText->reflowTextToFit(0);
33847 legendTextPos.h = legendText->getNumTextLines() * actualFontHeight;
33848 legendTextPos.y = tm->pos.y + tm->pos.h / 2 /*+ 2*/;
33849 legendTextPos.h += 4; // handle hanging chars
33850 legendText->setSize(legendTextPos);
33851
33852 auto ml = legendFrame->findImage("middle left img");
33853 ml->pos.h = legendTextPos.h - tm->pos.h;
33854 auto mm = legendFrame->findImage("middle img");
33855 mm->pos.h = ml->pos.h;
33856 auto mr = legendFrame->findImage("middle right img");
33857 mr->pos.h = ml->pos.h;
33858 mm->pos.w = legendPos.w - ml->pos.w - mr->pos.w;
33859 mr->pos.x = legendPos.w - mr->pos.w;
33860
33861 auto bl = legendFrame->findImage("bottom left img");
33862 bl->pos.y = ml->pos.y + ml->pos.h;
33863 auto bm = legendFrame->findImage("bottom img");
33864 bm->pos.y = bl->pos.y;
33865 auto br = legendFrame->findImage("bottom right img");
33866 br->pos.y = bl->pos.y;
33867 bm->pos.w = legendPos.w - bl->pos.w - br->pos.w;
33868 br->pos.x = legendPos.w - br->pos.w;
33869
33870
33871 legendPos.h = bl->pos.y + bl->pos.h - 4;
33872 legendFrame->setSize(legendPos);
33873
33874
33875 if ( proficiencyValue < SKILL_LEVEL_LEGENDARY )
33876 {
33877 legendDivTxt->setTextColor(legendRegularColor);
33878 legendText->setTextColor(legendRegularColor);
33879
33880 std::string staticImgPath = "*#images/ui/SkillSheet/UI_Skills_LegendBox_Full_";
33881 staticImgPath += std::to_string(legendPos.w) + 'x' + std::to_string(legendPos.h) + ".png";
33882
33883 auto staticImg = Image::get(staticImgPath.c_str());
33884 if ( staticImg && staticImg->getWidth() > 0 && *cvar_skillsheet_optimise )
33885 {
33886 for ( auto img : legendFrame->getImages() )
33887 {
33888 img->disabled = true;
33889 }
33890 mm->disabled = false;
33891 mm->path = staticImgPath;
33892 mm->color = makeColorRGB(255, 255, 255);
33893 mm->pos = SDL_Rect{ 0, 0, legendPos.w, legendPos.h };
33894 }
33895 else
33896 {
33897 for ( auto img : legendFrame->getImages() )
33898 {
33899 img->disabled = false;
33900 }
33901 mm->pos.x = 18;
33902 mm->pos.y = 18;
33903 tl->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox_TL_00.png";
33904 tm->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox_T_00.png";
33905 tr->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox_TR_00.png";
33906
33907 ml->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox_ML_00.png";
33908 mm->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox_M_00.png";
33909 mr->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox_MR_00.png";
33910
33911 bl->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox_BL_00.png";
33912 bm->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox_B_00.png";
33913 br->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox_BR_00.png";
33914 }
33915 }
33916 else
33917 {
33918 legendDivTxt->setTextColor(legendGoldColor);
33919 legendText->setTextColor(legendGoldColor);
33920
33921 std::string staticImgPath = "*#images/ui/SkillSheet/UI_Skills_LegendBox100_Full_";
33922 staticImgPath += std::to_string(legendPos.w) + 'x' + std::to_string(legendPos.h) + ".png";
33923
33924 auto staticImg = Image::get(staticImgPath.c_str());
33925 if ( staticImg && staticImg->getWidth() > 0 && *cvar_skillsheet_optimise )
33926 {
33927 for ( auto img : legendFrame->getImages() )
33928 {
33929 img->disabled = true;
33930 }
33931 mm->disabled = false;
33932 mm->path = staticImgPath;
33933 mm->color = makeColorRGB(255, 255, 255);
33934 mm->pos = SDL_Rect{ 0, 0, legendPos.w, legendPos.h };
33935 }
33936 else
33937 {
33938 for ( auto img : legendFrame->getImages() )
33939 {
33940 img->disabled = false;
33941 }
33942 mm->pos.x = 18;
33943 mm->pos.y = 18;
33944 tl->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox100_TL_00.png";
33945 tm->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox100_T_00.png";
33946 tr->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox100_TR_00.png";
33947
33948 ml->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox100_ML_00.png";
33949 mm->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox100_M_00.png";
33950 mr->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox100_MR_00.png";
33951
33952 bl->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox100_BL_00.png";
33953 bm->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox100_B_00.png";
33954 br->path = "*#images/ui/SkillSheet/UI_Skills_LegendBox100_BR_00.png";
33955 }
33956 }
33957 }
33958
33959 lowestY = legendPos.y + legendPos.h;
33960 if ( lowestY < scrollAreaOuterFrame->getSize().h )
33961 {
33962 // shift the legend items as far to the bottom as possible.
33963 int offset = scrollAreaOuterFrame->getSize().h - lowestY;
33964 legendDivImg->pos.y += offset;
33965 legendBackerImg->pos.y += offset;
33966 legendDivTxt->setPos(legendDivTxt->getSize().x, legendDivTxt->getSize().y + offset);
33967 legendFrame->setPos(legendFrame->getSize().x, legendFrame->getSize().y + offset);
33968 }
33969 //lowestY += 4; // small buffer after legend box
33970 }
33971
33972 auto oldScrollPercent = scrollPercent;
33973 if ( !slider->isDisabled() && player.bControlEnabled && !gamePaused && !player.usingCommand() )
33974 {
33975 if ( inputs.bPlayerUsingKeyboardControl(player.playernum) )
33976 {
33977 if ( Input::inputs[player.playernum].binaryToggle("MenuMouseWheelDownAlt") )
33978 {
33979 //Input::inputs[player.playernum].consumeBinaryToggle("MenuMouseWheelDownAlt");
33980 scrollInertia = std::min(scrollInertia + .05, .05);
33981 }
33982 if ( Input::inputs[player.playernum].binaryToggle("MenuMouseWheelUpAlt") )
33983 {
33984 //Input::inputs[player.playernum].consumeBinaryToggle("MenuMouseWheelUpAlt");
33985 scrollInertia = std::max(scrollInertia - .05, -.05);
33986 }
33987 }
33988 if ( Input::inputs[player.playernum].analog("MenuScrollDown") )
33989 {
33990 scrollInertia = 0.0;
33991 real_t delta = Input::inputs[player.playernum].analog("MenuScrollDown");
33992 scrollPercent = std::min(1.0, scrollPercent + .05 * (getFPSScale(60.0)) * delta);
33993 slider->setValue(scrollPercent * 100);
33994 }
33995 else if ( Input::inputs[player.playernum].analog("MenuScrollUp") )
33996 {
33997 scrollInertia = 0.0;
33998 real_t delta = Input::inputs[player.playernum].analog("MenuScrollUp");
33999 scrollPercent = std::max(0.0, scrollPercent -.05 * (getFPSScale(60.0)) * delta);
34000 slider->setValue(scrollPercent * 100);
34001 }
34002 }
34003
34004 if ( abs(scrollInertia) > 0.0 )
34005 {
34006 scrollInertia *= .9;
34007 if ( abs(scrollInertia) < .01 )
34008 {
34009 scrollInertia = 0.0;
34010 }
34011 scrollPercent = std::min(1.0, std::max(scrollPercent + scrollInertia, 0.00));
34012 if ( scrollPercent >= 1.0 || scrollPercent <= 0.0 )
34013 {
34014 scrollInertia = 0.0;
34015 }
34016 slider->setValue(scrollPercent * 100);
34017 }
34018
34019 scrollPercent = slider->getValue() / 100.0;
34020 scrollAreaPos = scrollArea->getSize();
34021 if ( lowestY > scrollAreaOuterFrame->getSize().h )
34022 {
34023 SDL_Rect actualSize = scrollArea->getActualSize();
34024 actualSize.y = (lowestY - scrollAreaOuterFrame->getSize().h) * scrollPercent;
34025 actualSize.h = scrollAreaPos.h;
34026 scrollArea->setActualSize(actualSize);
34027
34028 scrollAreaPos.y = 0;
34029 slider->setDisabled(false);
34030 }
34031 else
34032 {
34033 SDL_Rect actualSize = scrollArea->getActualSize();
34034 actualSize.y = 0;
34035 scrollArea->setActualSize(actualSize);
34036
34037 scrollAreaPos.y = 0;
34038 slider->setDisabled(true);
34039 }
34040 if ( scrollPercent != oldScrollPercent )
34041 {
34042 reblitFrame = true;
34043 }
34044 scrollArea->setSize(scrollAreaPos);
34045
34046 if ( !slider->isDisabled() && inputs.getVirtualMouse(player.playernum)->draw_cursor )
34047 {
34048 sliderSkillsheetUpdateSelectorOnHighlight(player.playernum, slider);
34049 }
34050 }
34051
34052 if ( oldHighlightedSkill != highlightedSkill )
34053 {
34054 reblitFrame = true;
34055 }
34056
34057 //DebugTimers.addTimePoint("skill", "post display");
34058
34059 bool drawGlyphs = !::inputs.getVirtualMouse(player.playernum)->draw_cursor && inputs.hasController(player.playernum);
34060 if ( auto promptBack = skillFrame->findField("prompt back txt") )
34061 {
34062 promptBack->setDisabled(!drawGlyphs);
34063 promptBack->setText(Language::get(4053));
34064 auto promptImg = skillFrame->findImage("prompt back img");
34065 promptImg->disabled = !drawGlyphs;
34066 SDL_Rect glyphPos = promptImg->pos;
34067 if ( auto textGet = Text::get(promptBack->getText(), promptBack->getFont(),
34068 promptBack->getTextColor(), promptBack->getOutlineColor()) )
34069 {
34070 SDL_Rect textPos = promptBack->getSize();
34071 textPos.w = textGet->getWidth();
34072 textPos.x = innerFrame->getSize().x + innerFrame->getSize().w - textPos.w - 16;
34073 textPos.y = innerFrame->getSize().y + allSkillEntriesLeft->getSize().y + lowestSkillEntryY + 4;
34074 if ( !bUseCompactSkillsView )
34075 {
34076 textPos.y -= 4;
34077 }
34078 promptBack->setSize(textPos);
34079 glyphPos.x = promptBack->getSize().x + promptBack->getSize().w - textGet->getWidth() - 4;
34080 }
34081 promptImg->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuCancel");
34082 Image* glyphImage = Image::get(promptImg->path.c_str());
34083 if ( glyphImage )
34084 {
34085 glyphPos.w = glyphImage->getWidth();
34086 glyphPos.h = glyphImage->getHeight();
34087 glyphPos.x -= glyphPos.w;
34088 glyphPos.y = promptBack->getSize().y + promptBack->getSize().h / 2 - glyphPos.h / 2;
34089 promptImg->pos = glyphPos;
34090 }
34091 }
34092 if ( auto promptScroll = skillFrame->findField("prompt scroll txt") )
34093 {
34094 promptScroll->setDisabled(!drawGlyphs);
34095 if ( bUseCompactSkillsView )
34096 {
34097 promptScroll->setText(Language::get(4063));
34098 }
34099 else
34100 {
34101 promptScroll->setText(Language::get(4062));
34102 }
34103 auto promptImg = skillFrame->findImage("prompt scroll img");
34104 promptImg->disabled = !drawGlyphs;
34105 SDL_Rect glyphPos = promptImg->pos;
34106 if ( auto textGet = Text::get(promptScroll->getText(), promptScroll->getFont(),
34107 promptScroll->getTextColor(), promptScroll->getOutlineColor()) )
34108 {
34109 SDL_Rect textPos = promptScroll->getSize();
34110 textPos.w = textGet->getWidth();
34111 if ( bUseCompactSkillsView )
34112 {
34113 textPos.x = innerFrame->getSize().x + 16;
34114 textPos.y = innerFrame->getSize().y + allSkillEntriesLeft->getSize().y + lowestSkillEntryY + 4;
34115 }
34116 else
34117 {
34118 textPos.x = innerFrame->getSize().x + innerFrame->getSize().w / 2 - textPos.w / 2;
34119 textPos.y = innerFrame->getSize().y + innerFrame->getSize().h - 8;
34120 }
34121 promptScroll->setSize(textPos);
34122 }
34123 promptImg->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuScrollDown");
34124 Image* glyphImage = Image::get(promptImg->path.c_str());
34125 if ( glyphImage )
34126 {
34127 SDL_Rect textPos = promptScroll->getSize();
34128 glyphPos.w = glyphImage->getWidth();
34129 glyphPos.h = glyphImage->getHeight();
34130 glyphPos.x = textPos.x;
34131 if ( !bUseCompactSkillsView ) // centred
34132 {
34133 glyphPos.x -= glyphPos.w / 2 + 2;
34134 }
34135 glyphPos.y = promptScroll->getSize().y + promptScroll->getSize().h / 2 - glyphPos.h / 2;
34136 promptImg->pos = glyphPos;
34137
34138 textPos.x = glyphPos.x + glyphPos.w + 4;
34139 promptScroll->setSize(textPos);
34140 }
34141 }
34142
34143 {
34144 // close btn
34145 auto closeBtn = innerFrame->findButton("close skills button");
34146 closeBtn->setDisabled(true);
34147 if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
34148 {
34149 closeBtn->setDisabled(!(skillsFadeInAnimationY >= 0.999));
34150 closeBtn->setInvisible(false);
34151 if ( !closeBtn->isDisabled() )
34152 {
34153 buttonSkillsheetUpdateSelectorOnHighlight(player.playernum, closeBtn);
34154 }
34155 }
34156 else
34157 {
34158 closeBtn->setDisabled(true);
34159 closeBtn->setInvisible(true);
34160 if ( closeBtn->isSelected() )
34161 {
34162 closeBtn->deselect();
34163 }
34164 }
34165
34166 SDL_Rect closeBtnPos = closeBtn->getSize();
34167 SDL_Rect bgImgPos = bgImgFrame->getSize();
34168 closeBtnPos.x = bgImgPos.x + bgImgPos.w - 38;
34169 closeBtnPos.y = bgImgPos.y + 22;
34170 closeBtn->setSize(closeBtnPos);
34171 }
34172
34173 if ( closeSheetAction || mouseClickedOutOfBounds )
34174 {
34175 if ( mouseClickedOutOfBounds )
34176 {
34177 Input::inputs[player.playernum].consumeBinaryToggle("MenuLeftClick");
34178 Input::inputs[player.playernum].consumeBindingsSharedWithBinding("MenuLeftClick");
34179 if ( inputs.bPlayerUsingKeyboardControl(player.playernum) )
34180 {
34181 mousestatus[SDL_BUTTON_LEFT1] = 0;
34182 }
34183 }
34184 closeSkillSheet();
34185 Player::soundCancel();
34186 return;
34187 }
34188
34189 {
34190 if ( !bSkillSheetEntryLoaded || reblitFrame || skillsFadeInAnimationY < 1.0 )
34191 {
34192 skillSheetEntryFrames[player.playernum].skillsFrame->setBlitDirty(true);
34193 }
34194 }
34195
34196 if ( sliderDisabled != slider->isDisabled() )
34197 {
34198 // rerun this function
34199 processSkillSheet();
34200 //bSkillSheetEntryLoaded = false;
34201 }
34202 else
34203 {
34204 bSkillSheetEntryLoaded = true;
34205 }
34206 //DebugTimers.addTimePoint("skill", "end");
34207 //DebugTimers.addTimePoint("skill 1", "end");
34208}
34209
34210void Player::Inventory_t::SpellPanel_t::openSpellPanel()
34211{
34212 if ( player.inventoryUI.spellFrame )
34213 {
34214 bool wasDisabled = player.inventoryUI.spellFrame->isDisabled();
34215 player.inventoryUI.spellFrame->setDisabled(false);
34216 if ( wasDisabled )
34217 {
34218 animx = 0.0;
34219 isInteractable = false;
34220 currentScrollRow = 0;
34221 scrollPercent = 0.0;
34222 scrollInertia = 0.0;
34223 bFirstTimeSnapCursor = false;
34224 if ( !inputs.getUIInteraction(player.playernum)->selectedItem )
34225 {
34226 Player::soundActivate();
34227 }
34228 }
34229 bOpen = true;
34230 }
34231}
34232
34233void Player::Inventory_t::SpellPanel_t::closeSpellPanel()
34234{
34235 if ( player.inventoryUI.spellFrame )
34236 {
34237 player.inventoryUI.spellFrame->setDisabled(true);
34238 }
34239 animx = 0.0;
34240 isInteractable = false;
34241 currentScrollRow = 0;
34242 scrollPercent = 0.0;
34243 scrollInertia = 0.0;
34244 scrollAnimateX = scrollSetpoint;
34245 if ( bOpen
34246 && !(player.inventoryUI.bCompactView
34247 && player.hud.compactLayoutMode == Player::HUD_t::COMPACT_LAYOUT_CHARSHEET) )
34248 {
34249 if ( !inputs.getUIInteraction(player.playernum)->selectedItem )
34250 {
34251 Player::soundCancel();
34252 }
34253 }
34254 bOpen = false;
34255 bFirstTimeSnapCursor = false;
34256}
34257
34258int Player::Inventory_t::SpellPanel_t::getNumSpellsToDisplayVertical() const
34259{
34260 if ( !player.bUseCompactGUIHeight() )
34261 {
34262 return kNumSpellsToDisplayVertical;
34263 }
34264 else
34265 {
34266 return kNumSpellsToDisplayVertical;
34267 }
34268}
34269
34270void buttonSpellUpdateSelectorOnHighlight(const int player, Button* button)
34271{
34272 if ( button->isHighlighted() )
34273 {
34274 players[player]->GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_SPELLS);
34275 if ( players[player]->GUI.activeModule != Player::GUI_t::MODULE_SPELLS )
34276 {
34277 players[player]->GUI.activateModule(Player::GUI_t::MODULE_SPELLS);
34278 }
34279 SDL_Rect pos = button->getAbsoluteSize();
34280 // make sure to adjust absolute size to camera viewport
34281 pos.x -= players[player]->camera_virtualx1();
34282 pos.y -= players[player]->camera_virtualy1();
34283 players[player]->hud.setCursorDisabled(false);
34284 players[player]->hud.updateCursorAnimation(pos.x - 1, pos.y - 1, pos.w, pos.h, inputs.getVirtualMouse(player)->draw_cursor);
34285 }
34286}
34287
34288void sliderSpellUpdateSelectorOnHighlight(const int player, Slider* slider)
34289{
34290 if ( slider->isHighlighted() )
34291 {
34292 players[player]->GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_SPELLS);
34293 if ( players[player]->GUI.activeModule != Player::GUI_t::MODULE_SPELLS )
34294 {
34295 players[player]->GUI.activateModule(Player::GUI_t::MODULE_SPELLS);
34296 }
34297 SDL_Rect pos = slider->getAbsoluteSize();
34298 // make sure to adjust absolute size to camera viewport
34299 pos.x -= players[player]->camera_virtualx1();
34300 pos.y -= players[player]->camera_virtualy1();
34301 players[player]->hud.setCursorDisabled(false);
34302 players[player]->hud.updateCursorAnimation(pos.x - 1, pos.y - 1, pos.w, pos.h, inputs.getVirtualMouse(player)->draw_cursor);
34303 }
34304}
34305
34306int Player::Inventory_t::SpellPanel_t::heightOffsetWhenNotCompact = 0;
34307void Player::Inventory_t::SpellPanel_t::updateSpellPanel()
34308{
34309 Frame* spellFrame = player.inventoryUI.spellFrame;
34310 if ( !spellFrame ) { return; }
34311
34312 if ( !spellFrame->isDisabled() && bOpen )
34313 {
34314 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
34315 real_t setpointDiffX = fpsScale * std::max(.01, (1.0 - animx)) / 2.0;
34316 animx += setpointDiffX;
34317 animx = std::min(1.0, animx);
34318 if ( animx >= .9999 )
34319 {
34320 if ( !bFirstTimeSnapCursor )
34321 {
34322 bFirstTimeSnapCursor = true;
34323 if ( !inputs.getUIInteraction(player.playernum)->selectedItem
34324 && player.GUI.activeModule == Player::GUI_t::MODULE_SPELLS )
34325 {
34326 player.inventoryUI.warpMouseToSelectedSpell(nullptr, (Inputs::SET_CONTROLLER));
34327 }
34328 }
34329 isInteractable = true;
34330 }
34331 }
34332 else
34333 {
34334 animx = 0.0;
34335 isInteractable = false;
34336 scrollInertia = 0.0;
34337 }
34338 auto spellFramePos = spellFrame->getSize();
34339 if ( player.inventoryUI.inventoryPanelJustify == Player::PANEL_JUSTIFY_LEFT )
34340 {
34341 spellFramePos.x = -spellFramePos.w + animx * spellFramePos.w;
34342 if ( player.bUseCompactGUIWidth() )
34343 {
34344 if ( player.inventoryUI.slideOutPercent >= .0001 )
34345 {
34346 isInteractable = false;
34347 }
34348 spellFramePos.x -= player.inventoryUI.slideOutWidth * player.inventoryUI.slideOutPercent;
34349 }
34350 }
34351 else
34352 {
34353 spellFramePos.x = player.camera_virtualWidth() - animx * spellFramePos.w;
34354 if ( player.bUseCompactGUIWidth() )
34355 {
34356 if ( player.inventoryUI.slideOutPercent >= .0001 )
34357 {
34358 isInteractable = false;
34359 }
34360 spellFramePos.x -= -player.inventoryUI.slideOutWidth * player.inventoryUI.slideOutPercent;
34361 }
34362 }
34363 spellFrame->setSize(spellFramePos);
34364
34365 auto baseFrame = spellFrame->findFrame("spell base");
34366 auto slider = baseFrame->findSlider("spell slider");
34367 auto spellSlotsFrame = spellFrame->findFrame("spell slots");
34368 auto baseBackgroundImg = baseFrame->findImage("spell base img");
34369 // handle height changing..
34370 {
34371 int frameHeight = kSpellListHeight;
34372 int totalFrameHeightChange = 0;
34373 if ( !player.bUseCompactGUIHeight() )
34374 {
34375 totalFrameHeightChange = heightOffsetWhenNotCompact;
34376 }
34377 spellFramePos.h = frameHeight + totalFrameHeightChange;
34378 spellFrame->setSize(spellFramePos);
34379 baseBackgroundImg->pos.y = totalFrameHeightChange;
34380
34381 SDL_Rect spellBasePos = baseFrame->getSize();
34382 spellBasePos.h = spellFramePos.h;
34383 baseFrame->setSize(spellBasePos);
34384
34385 int numGrids = (players[player.playernum]->inventoryUI.MAX_SPELLS_Y / getNumSpellsToDisplayVertical()) + 1;
34386 SDL_Rect spellSlotsFramePos = spellSlotsFrame->getSize();
34387
34388 int heightChange = 0;
34389 if ( getNumSpellsToDisplayVertical() < kNumSpellsToDisplayVertical )
34390 {
34391 heightChange = player.inventoryUI.getSlotSize() * (kNumSpellsToDisplayVertical - getNumSpellsToDisplayVertical());
34392 }
34393 spellSlotsFramePos.y = 4 + heightChange + totalFrameHeightChange;
34394 spellSlotsFramePos.h = 242 - heightChange;
34395 spellSlotsFrame->setActualSize(SDL_Rect{ spellSlotsFrame->getActualSize().x,
34396 spellSlotsFrame->getActualSize().y,
34397 spellSlotsFrame->getActualSize().w,
34398 (spellSlotsFramePos.h) * numGrids });
34399 spellSlotsFrame->setSize(spellSlotsFramePos);
34400 auto gridImg = spellSlotsFrame->findImage("grid img");
34401 gridImg->pos.y = kSpellListGridY;
34402 gridImg->pos.h = (spellSlotsFramePos.h) * numGrids;
34403
34404 SDL_Rect sliderPos = slider->getRailSize();
34405 sliderPos.y = 50 + heightChange + totalFrameHeightChange;
34406 sliderPos.h = 192 - heightChange;
34407 slider->setRailSize(sliderPos);
34408
34409 auto sliderCapTop = baseFrame->findImage("spell slider top");
34410 auto sliderCapBot = baseFrame->findImage("spell slider bot");
34411 sliderCapTop->pos.y = sliderPos.y;
34412 sliderCapBot->pos.y = sliderPos.y + sliderPos.h - sliderCapBot->pos.h;
34413 }
34414
34415 auto skillBg = baseFrame->findImage("spell skill bg");
34416 skillBg->pos.x = 14;
34417 skillBg->pos.y = 6;
34418
34419 int lowestItemY = getNumSpellsToDisplayVertical() - 1;
34420 for ( node_t* node = stats[player.playernum]->inventory.first; node != NULL__null; node = node->next )
34421 {
34422 Item* item = (Item*)node->element;
34423 if ( !item ) { continue; }
34424 if ( itemCategory(item) != SPELL_CAT ) { continue; }
34425
34426 lowestItemY = std::max(lowestItemY, item->y);
34427 }
34428
34429 int scrollAmount = std::max((lowestItemY + 1) - (getNumSpellsToDisplayVertical()), 0) * player.inventoryUI.getSlotSize();
34430 if ( scrollAmount == 0 )
34431 {
34432 slider->setDisabled(true);
34433 }
34434 else
34435 {
34436 if ( bOpen )
34437 {
34438 slider->setDisabled(false);
34439 }
34440 else
34441 {
34442 slider->setDisabled(true);
34443 }
34444 }
34445
34446 currentScrollRow = scrollSetpoint / player.inventoryUI.getSlotSize();
34447
34448 bool usingGamepad = inputs.hasController(player.playernum) && !inputs.getVirtualMouse(player.playernum)->draw_cursor;
34449 // close btn
34450 auto closeBtn = baseFrame->findButton("close spell button");
34451 auto closeGlyph = baseFrame->findImage("close spell glyph");
34452 closeBtn->setDisabled(true);
34453 SDL_Rect closeBtnPos = closeBtn->getSize();
34454 closeBtnPos.x = 166;
34455 closeBtnPos.y = 8;
34456 closeBtn->setSize(closeBtnPos);
34457 closeGlyph->disabled = true;
34458 if ( inputs.getVirtualMouse(player.playernum)->draw_cursor )
34459 {
34460 closeBtn->setDisabled(!isInteractable);
34461 if ( isInteractable )
34462 {
34463 buttonSpellUpdateSelectorOnHighlight(player.playernum, closeBtn);
34464 }
34465 }
34466 else if ( closeBtn->isSelected() )
34467 {
34468 closeBtn->deselect();
34469 }
34470 if ( closeBtn->isDisabled() && usingGamepad )
34471 {
34472 SDL_Rect closeBtnPos = closeBtn->getSize();
34473 closeBtnPos.x -= 10;
34474 closeBtn->setSize(closeBtnPos);
34475
34476 closeGlyph->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuCancel");
34477 if ( auto imgGet = Image::get(closeGlyph->path.c_str()) )
34478 {
34479 closeGlyph->pos.w = imgGet->getWidth();
34480 closeGlyph->pos.h = imgGet->getHeight();
34481 closeGlyph->disabled = false;
34482 }
34483 closeGlyph->pos.x = closeBtn->getSize().x + closeBtn->getSize().w;
34484 if ( closeGlyph->pos.x % 2 == 1 )
34485 {
34486 ++closeGlyph->pos.x;
34487 }
34488 closeGlyph->pos.y = closeBtn->getSize().y + closeBtn->getSize().h / 2 - closeGlyph->pos.w / 2;
34489 if ( closeGlyph->pos.y % 2 == 1 )
34490 {
34491 ++closeGlyph->pos.y;
34492 }
34493 }
34494
34495 auto skillIcon = baseFrame->findImage("spell skill icon");
34496 skillIcon->pos.x = skillBg->pos.x + 4;
34497 skillIcon->pos.y = skillBg->pos.y + 4;
34498 for ( auto& skillEntry : Player::SkillSheet_t::skillSheetData.skillEntries )
34499 {
34500 if ( skillEntry.skillId == PRO_MAGIC )
34501 {
34502 if ( skillCapstoneUnlocked(player.playernum, PRO_MAGIC) )
34503 {
34504 skillIcon->path = skillEntry.skillIconPathLegend.c_str();
34505 }
34506 else
34507 {
34508 skillIcon->path = skillEntry.skillIconPath.c_str();
34509 }
34510 break;
34511 }
34512 }
34513
34514 if ( bOpen && isInteractable )
34515 {
34516 // do sliders
34517 if ( !slider->isDisabled() )
34518 {
34519 if ( !inputs.getUIInteraction(player.playernum)->selectedItem
34520 && player.GUI.activeModule == Player::GUI_t::MODULE_SPELLS )
34521 {
34522 auto& input = Input::inputs[player.playernum];
34523 if ( !player.inventoryUI.itemTooltipDisplay.scrollable )
34524 {
34525 if ( inputs.bPlayerUsingKeyboardControl(player.playernum) )
34526 {
34527 if ( input.binaryToggle("MenuMouseWheelDown") )
34528 {
34529 scrollSetpoint = std::max(scrollSetpoint + player.inventoryUI.getSlotSize(), 0);
34530 }
34531 if ( input.binaryToggle("MenuMouseWheelUp") )
34532 {
34533 scrollSetpoint = std::max(scrollSetpoint - player.inventoryUI.getSlotSize(), 0);
34534 }
34535 }
34536
34537 if ( input.consumeBinaryToggle("MenuScrollDown") )
34538 {
34539 scrollSetpoint = std::max(scrollSetpoint + player.inventoryUI.getSlotSize(), 0);
34540 if ( player.inventoryUI.cursor.queuedModule == Player::GUI_t::MODULE_SPELLS )
34541 {
34542 player.inventoryUI.cursor.queuedModule = Player::GUI_t::MODULE_NONE;
34543 }
34544 }
34545 else if ( input.consumeBinaryToggle("MenuScrollUp") )
34546 {
34547 scrollSetpoint = std::max(scrollSetpoint - player.inventoryUI.getSlotSize(), 0);
34548 if ( player.inventoryUI.cursor.queuedModule == Player::GUI_t::MODULE_SPELLS )
34549 {
34550 player.inventoryUI.cursor.queuedModule = Player::GUI_t::MODULE_NONE;
34551 }
34552 }
34553 }
34554 }
34555 }
34556
34557 scrollSetpoint = std::min(scrollSetpoint, scrollAmount);
34558 currentScrollRow = scrollSetpoint / player.inventoryUI.getSlotSize();
34559
34560 if ( abs(scrollSetpoint - scrollAnimateX) > 0.00001 )
34561 {
34562 isInteractable = false;
34563 player.inventoryUI.tooltipDelayTick = ticks + TICKS_PER_SECOND50 / 10;
34564 const real_t fpsScale = getFPSScale(60.0);
34565 real_t setpointDiff = 0.0;
34566
34567 // slightly faster on gamepad
34568 static ConsoleVariable<float> cvar_spell_slider_speed("/spell_slider_speed", 1.f);
34569 const real_t factor = (3.0 * (*cvar_spell_slider_speed + (usingGamepad ? -.25f : 0.f)));
34570 if ( scrollSetpoint - scrollAnimateX > 0.0 )
34571 {
34572 setpointDiff = fpsScale * std::max(3.0, (scrollSetpoint - scrollAnimateX)) / factor;
34573 }
34574 else
34575 {
34576 setpointDiff = fpsScale * std::min(-3.0, (scrollSetpoint - scrollAnimateX)) / factor;
34577 }
34578 scrollAnimateX += setpointDiff;
34579 if ( setpointDiff > 0.0 )
34580 {
34581 scrollAnimateX = std::min((real_t)scrollSetpoint, scrollAnimateX);
34582 }
34583 else
34584 {
34585 scrollAnimateX = std::max((real_t)scrollSetpoint, scrollAnimateX);
34586 }
34587 }
34588 else
34589 {
34590 scrollAnimateX = scrollSetpoint;
34591 }
34592
34593 if ( !inputs.getUIInteraction(player.playernum)->selectedItem
34594 && !player.GUI.isDropdownActive()
34595 && player.GUI.bModuleAccessibleWithMouse(Player::GUI_t::MODULE_SPELLS)
34596 && player.bControlEnabled && !gamePaused && !player.usingCommand() )
34597 {
34598 if ( Input::inputs[player.playernum].binaryToggle("MenuCancel") )
34599 {
34600 Input::inputs[player.playernum].consumeBinaryToggle("MenuCancel");
34601 player.GUI.activateModule(Player::GUI_t::MODULE_SPELLS);
34602 player.inventoryUI.cycleInventoryTab();
34603 player.inventoryUI.spellPanel.closeSpellPanel();
34604 }
34605 }
34606 }
34607
34608 if ( scrollAmount > 0 )
34609 {
34610 if ( !slider->isDisabled() && !usingGamepad && isInteractable )
34611 {
34612 sliderSpellUpdateSelectorOnHighlight(player.playernum, slider);
34613 }
34614 if ( slider->isCurrentlyPressed() )
34615 {
34616 auto val = slider->getValue() / 100.0;
34617 int animX = val * scrollAmount;
34618 animX /= player.inventoryUI.getSlotSize();
34619 animX *= player.inventoryUI.getSlotSize();
34620
34621 scrollSetpoint = animX;
34622 scrollSetpoint = std::min(scrollSetpoint, scrollAmount);
34623 }
34624 else
34625 {
34626 slider->setValue((scrollAnimateX / scrollAmount) * 100.0);
34627 }
34628 }
34629 else
34630 {
34631 slider->setValue(0.0);
34632 }
34633
34634 SDL_Rect actualSize = spellSlotsFrame->getActualSize();
34635 actualSize.y = scrollAnimateX;
34636 spellSlotsFrame->setActualSize(actualSize);
34637}
34638
34639bool Player::Inventory_t::SpellPanel_t::isSlotVisible(int x, int y) const
34640{
34641 if ( player.inventoryUI.spellFrame )
34642 {
34643 if ( player.inventoryUI.spellFrame->isDisabled() )
34644 {
34645 return false;
34646 }
34647 }
34648 int lowerY = currentScrollRow;
34649 int upperY = currentScrollRow + getNumSpellsToDisplayVertical() - 1;
34650
34651 if ( y >= lowerY && y <= upperY )
34652 {
34653 return true;
34654 }
34655 return false;
34656}
34657
34658bool Player::Inventory_t::SpellPanel_t::isItemVisible(Item* item) const
34659{
34660 if ( !item ) { return false; }
34661 return isSlotVisible(item->x, item->y);
34662}
34663
34664void Player::Inventory_t::SpellPanel_t::scrollToSlot(int x, int y, bool instantly)
34665{
34666 int lowerY = currentScrollRow;
34667 int upperY = currentScrollRow + getNumSpellsToDisplayVertical() - 1;
34668
34669 if ( y >= lowerY && y <= upperY )
34670 {
34671 // no work to do.
34672 return;
34673 }
34674
34675 int lowestItemY = getNumSpellsToDisplayVertical() - 1;
34676 for ( node_t* node = stats[player.playernum]->inventory.first; node != NULL__null; node = node->next )
34677 {
34678 Item* item = (Item*)node->element;
34679 if ( !item ) { continue; }
34680 if ( itemCategory(item) != SPELL_CAT ) { continue; }
34681
34682 lowestItemY = std::max(lowestItemY, item->y);
34683 }
34684 int maxScroll = std::max((lowestItemY + 1) - (getNumSpellsToDisplayVertical()), 0) * player.inventoryUI.getSlotSize();
34685
34686 int scrollAmount = 0;
34687 if ( y < lowerY )
34688 {
34689 scrollAmount = (y) * player.inventoryUI.getSlotSize();
34690 //scrollAmount += scrollSetpoint;
34691 }
34692 else if ( y > upperY )
34693 {
34694 scrollAmount = (y - upperY) * player.inventoryUI.getSlotSize();
34695 scrollAmount += scrollSetpoint;
34696 }
34697 scrollAmount = std::min(scrollAmount, maxScroll);
34698
34699 scrollSetpoint = scrollAmount;
34700 if ( instantly )
34701 {
34702 scrollAnimateX = scrollSetpoint;
34703 }
34704 currentScrollRow = scrollSetpoint / player.inventoryUI.getSlotSize();
34705 if ( abs(scrollSetpoint - scrollAnimateX) > 0.00001 )
34706 {
34707 isInteractable = false;
34708 }
34709}
34710
34711void Player::Inventory_t::ChestGUI_t::openChest()
34712{
34713 if ( player.inventoryUI.chestFrame )
34714 {
34715 bool wasDisabled = player.inventoryUI.chestFrame->isDisabled();
34716 player.inventoryUI.chestFrame->setDisabled(false);
34717 if ( wasDisabled )
34718 {
34719 animx = 0.0;
34720 isInteractable = false;
34721 currentScrollRow = 0;
34722 scrollPercent = 0.0;
34723 scrollInertia = 0.0;
34724 bFirstTimeSnapCursor = false;
34725 }
34726 /*if ( player.inventoryUI.getSelectedChestX() < 0 || player.inventoryUI.getSelectedChestX() >= MAX_CHEST_X
34727 || player.inventoryUI.getSelectedChestY() < 0 || player.inventoryUI.getSelectedChestY() >= MAX_CHEST_Y )*/
34728 {
34729 player.inventoryUI.selectChestSlot(0, 0); // always select first slot
34730 }
34731 player.hud.compactLayoutMode = Player::HUD_t::COMPACT_LAYOUT_INVENTORY;
34732 player.inventory_mode = INVENTORY_MODE_ITEM;
34733 bOpen = true;
34734 }
34735 if ( inputs.getUIInteraction(player.playernum)->selectedItem )
34736 {
34737 inputs.getUIInteraction(player.playernum)->selectedItem = nullptr;
34738 inputs.getUIInteraction(player.playernum)->toggleclick = false;
34739 }
34740 inputs.getUIInteraction(player.playernum)->selectedItemFromChest = 0;
34741}
34742
34743bool Player::Inventory_t::ChestGUI_t::isChestSelected()
34744{
34745 if ( !bOpen )
34746 {
34747 return false;
34748 }
34749
34750 if ( player.GUI.activeModule == Player::GUI_t::MODULE_CHEST )
34751 {
34752 if ( selectedChestSlotX >= 0 && selectedChestSlotX < MAX_CHEST_X
34753 && selectedChestSlotY >= 0 && selectedChestSlotY < MAX_CHEST_Y )
34754 {
34755 return true;
34756 }
34757 }
34758
34759 return false;
34760}
34761
34762void Player::Inventory_t::ChestGUI_t::closeChest()
34763{
34764 if ( player.inventoryUI.chestFrame )
34765 {
34766 player.inventoryUI.chestFrame->setDisabled(true);
34767 }
34768 animx = 0.0;
34769 isInteractable = false;
34770 currentScrollRow = 0;
34771 scrollPercent = 0.0;
34772 scrollInertia = 0.0;
34773 scrollAnimateX = scrollSetpoint;
34774 bOpen = false;
34775 bFirstTimeSnapCursor = false;
34776 if ( inputs.getUIInteraction(player.playernum)->selectedItemFromChest > 0 )
34777 {
34778 inputs.getUIInteraction(player.playernum)->selectedItem = nullptr;
34779 inputs.getUIInteraction(player.playernum)->toggleclick = false;
34780 }
34781 inputs.getUIInteraction(player.playernum)->selectedItemFromChest = 0;
34782 if ( player.GUI.dropdownMenu.currentName == "chest_interact" )
34783 {
34784 player.GUI.dropdownMenu.close();
34785 }
34786 if ( players[player.playernum]->GUI.activeModule == Player::GUI_t::MODULE_CHEST
34787 && !players[player.playernum]->shootmode )
34788 {
34789 // reset to inventory mode if still hanging in chest GUI
34790 players[player.playernum]->hud.compactLayoutMode = Player::HUD_t::COMPACT_LAYOUT_INVENTORY;
34791 players[player.playernum]->GUI.activateModule(Player::GUI_t::MODULE_INVENTORY);
34792 if ( !inputs.getVirtualMouse(player.playernum)->draw_cursor )
34793 {
34794 players[player.playernum]->GUI.warpControllerToModule(false);
34795 }
34796 }
34797}
34798
34799int Player::Inventory_t::ChestGUI_t::getNumItemsToDisplayVertical() const
34800{
34801 return kNumItemsToDisplayVertical;
34802}
34803
34804void Player::Inventory_t::selectChestSlot(const int x, const int y)
34805{
34806 chestGUI.selectedChestSlotX = x;
34807 chestGUI.selectedChestSlotY = y;
34808}
34809
34810const bool Player::Inventory_t::isItemFromChest(Item* item) const
34811{
34812 if ( !item )
34813 {
34814 return false;
34815 }
34816
34817 if ( !openedChest[player.playernum] || !chestGUI.bOpen )
34818 {
34819 return false;
34820 }
34821
34822 list_t* chest_inventory = nullptr;
34823 if ( multiplayer == CLIENT2 )
34824 {
34825 chest_inventory = &chestInv[player.playernum];
34826 }
34827 else if ( openedChest[player.playernum]->children.first && openedChest[player.playernum]->children.first->element )
34828 {
34829 chest_inventory = (list_t*)openedChest[player.playernum]->children.first->element;
34830 }
34831
34832 if ( item->node && item->node->list == chest_inventory )
34833 {
34834 return true;
34835 }
34836 return false;
34837}
34838
34839int Player::Inventory_t::ChestGUI_t::heightOffsetWhenNotCompact = 0;
34840void Player::Inventory_t::ChestGUI_t::updateChest()
34841{
34842 updateChestInventory(player.playernum);
34843
34844 Frame* chestFrame = player.inventoryUI.chestFrame;
34845 if ( !chestFrame ) { return; }
34846
34847 auto baseFrame = chestFrame->findFrame("chest base");
34848 auto grabAllBtn = baseFrame->findButton("grab all button");
34849 auto closeBtn = baseFrame->findButton("close chest button");
34850 if ( !chestFrame->isDisabled() && bOpen )
34851 {
34852 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
34853 real_t setpointDiffX = fpsScale * std::max(.01, (1.0 - animx)) / 2.0;
34854 animx += setpointDiffX;
34855 animx = std::min(1.0, animx);
34856 if ( animx >= .9999 )
34857 {
34858 if ( !bFirstTimeSnapCursor )
34859 {
34860 bFirstTimeSnapCursor = true;
34861 if ( !inputs.getUIInteraction(player.playernum)->selectedItem
34862 && player.GUI.activeModule == Player::GUI_t::MODULE_CHEST )
34863 {
34864 player.inventoryUI.warpMouseToSelectedChestSlot(nullptr, (Inputs::SET_CONTROLLER));
34865 }
34866 }
34867 isInteractable = true;
34868 }
34869 }
34870 else
34871 {
34872 animx = 0.0;
34873 isInteractable = false;
34874 scrollInertia = 0.0;
34875 }
34876
34877 auto chestFramePos = chestFrame->getSize();
34878 auto baseBackgroundImg = baseFrame->findImage("chest base img");
34879 auto lidBackgroundImg = baseFrame->findImage("chest lid img");
34880
34881 int playercount = 0;
34882 for ( int c = 0; c < MAXPLAYERS4; ++c ) {
34883 if ( !client_disconnected[c] && players[c]->isLocalPlayer() ) {
34884 ++playercount;
34885 }
34886 }
34887
34888 if ( player.inventoryUI.inventoryPanelJustify == Player::PANEL_JUSTIFY_LEFT )
34889 {
34890 if ( !player.inventoryUI.bCompactView )
34891 {
34892 const int fullWidth = chestFramePos.w + 210; // inventory width 210
34893 chestFramePos.x = -chestFramePos.w + animx * fullWidth;
34894 if ( player.bUseCompactGUIWidth() )
34895 {
34896 if ( player.inventoryUI.slideOutPercent >= .0001 )
34897 {
34898 isInteractable = false;
34899 }
34900 chestFramePos.x -= player.inventoryUI.slideOutWidth * player.inventoryUI.slideOutPercent;
34901 }
34902 }
34903 else
34904 {
34905 if ( player.bAlignGUINextToInventoryCompact() )
34906 {
34907 const int fullWidth = chestFramePos.w + 210; // inventory width 210
34908 chestFramePos.x = -chestFramePos.w + animx * fullWidth;
34909 }
34910 else
34911 {
34912 chestFramePos.x = player.camera_virtualWidth() - animx * chestFramePos.w;
34913 }
34914 if ( player.bUseCompactGUIWidth() )
34915 {
34916 if ( player.inventoryUI.slideOutPercent >= .0001 )
34917 {
34918 isInteractable = false;
34919 }
34920 chestFramePos.x -= -player.inventoryUI.slideOutWidth * player.inventoryUI.slideOutPercent;
34921 }
34922 }
34923 }
34924 else if ( player.inventoryUI.inventoryPanelJustify == Player::PANEL_JUSTIFY_RIGHT )
34925 {
34926 if ( !player.inventoryUI.bCompactView )
34927 {
34928 chestFramePos.x = player.camera_virtualWidth() - animx * chestFramePos.w * 2;
34929 if ( player.bUseCompactGUIWidth() )
34930 {
34931 if ( player.inventoryUI.slideOutPercent >= .0001 )
34932 {
34933 isInteractable = false;
34934 }
34935 chestFramePos.x -= -player.inventoryUI.slideOutWidth * player.inventoryUI.slideOutPercent;
34936 }
34937 }
34938 else
34939 {
34940 if ( player.bAlignGUINextToInventoryCompact() )
34941 {
34942 const int fullWidth = chestFramePos.w + 210; // inventory width 210
34943 chestFramePos.x = player.camera_virtualWidth() - animx * fullWidth;
34944 }
34945 else
34946 {
34947 chestFramePos.x = -chestFramePos.w + animx * chestFramePos.w;
34948 }
34949 if ( player.bUseCompactGUIWidth() )
34950 {
34951 if ( player.inventoryUI.slideOutPercent >= .0001 )
34952 {
34953 isInteractable = false;
34954 }
34955 chestFramePos.x -= player.inventoryUI.slideOutWidth * player.inventoryUI.slideOutPercent;
34956 }
34957 }
34958 }
34959 chestFrame->setSize(chestFramePos);
34960
34961 bool drawGlyphs = !::inputs.getVirtualMouse(player.playernum)->draw_cursor;
34962 if ( !drawGlyphs )
34963 {
34964 closeBtn->setInvisible(false);
34965 grabAllBtn->setInvisible(false);
34966 grabAllBtn->setDisabled(!isInteractable);
34967 closeBtn->setDisabled(!isInteractable);
34968 }
34969 else
34970 {
34971 closeBtn->setInvisible(false);
34972 grabAllBtn->setInvisible(false);
34973 grabAllBtn->setDisabled(!isInteractable);
34974 closeBtn->setDisabled(!isInteractable);
34975 }
34976
34977 //auto slider = baseFrame->findSlider("chest slider");
34978 auto chestSlotsFrame = chestFrame->findFrame("chest slots");
34979
34980 auto promptBack = baseFrame->findField("prompt back txt");
34981 promptBack->setDisabled(true);
34982 auto promptBackImg = baseFrame->findImage("prompt back img");
34983 promptBackImg->disabled = !drawGlyphs || player.inventoryUI.spellPanel.bOpen;
34984 auto promptGrab = baseFrame->findField("prompt grab txt");
34985 promptGrab->setDisabled(true);
34986 auto promptGrabImg = baseFrame->findImage("prompt grab img");
34987 promptGrabImg->disabled = !drawGlyphs || player.inventoryUI.spellPanel.bOpen;
34988 // handle height changing..
34989 {
34990 int frameHeight = 236 + 16;
34991 if ( !player.bUseCompactGUIHeight() && !player.bUseCompactGUIWidth() )
34992 {
34993 chestFramePos.y = 130;
34994 }
34995 else
34996 {
34997 chestFramePos.y = 0;
34998 }
34999 chestFramePos.h = frameHeight;
35000 chestFrame->setSize(chestFramePos);
35001 baseBackgroundImg->pos.y = 16 + 48;
35002 lidBackgroundImg->pos.y = baseBackgroundImg->pos.y - lidBackgroundImg->pos.h + 2;
35003 SDL_Rect chestBasePos = baseFrame->getSize();
35004 chestBasePos.h = chestFramePos.h;
35005 baseFrame->setSize(chestBasePos);
35006
35007 int numGrids = (players[player.playernum]->inventoryUI.MAX_CHEST_Y / getNumItemsToDisplayVertical()) + 1;
35008 SDL_Rect chestSlotsFramePos = chestSlotsFrame->getSize();
35009
35010 const int gridHeight = 120 + 2; // 120px is grid img, plus 2px to tile the image for bottom border.
35011
35012 int heightChange = 0;
35013 if ( getNumItemsToDisplayVertical() < kNumItemsToDisplayVertical )
35014 {
35015 heightChange = player.inventoryUI.getSlotSize() * (kNumItemsToDisplayVertical - getNumItemsToDisplayVertical());
35016 }
35017 chestSlotsFramePos.y = 4 + chestBaseImgBorderTopHeight + heightChange + 48;
35018 chestSlotsFramePos.h = gridHeight - heightChange;
35019 chestSlotsFrame->setActualSize(SDL_Rect{ chestSlotsFrame->getActualSize().x,
35020 chestSlotsFrame->getActualSize().y,
35021 chestSlotsFrame->getActualSize().w,
35022 (chestSlotsFramePos.h) * numGrids });
35023 chestSlotsFrame->setSize(chestSlotsFramePos);
35024 //auto gridImg = chestSlotsFrame->findImage("grid img");
35025 //gridImg->pos.y = 0;
35026 //gridImg->pos.h = (chestSlotsFramePos.h) * numGrids;
35027
35028 /*SDL_Rect sliderPos = slider->getRailSize();
35029 sliderPos.y = 8 + heightChange + totalFrameHeightChange;
35030 sliderPos.h = 234 - heightChange;
35031 slider->setRailSize(sliderPos);*/
35032
35033 // align title
35034 auto titleText = baseFrame->findField("title txt");
35035 auto titlePos = titleText->getSize();
35036 titlePos.y = baseBackgroundImg->pos.y - 24;
35037 titlePos.x = baseBackgroundImg->pos.x + baseBackgroundImg->pos.w / 2 - titlePos.w / 2;
35038 titleText->setSize(titlePos);
35039
35040 chestFrame->setSize(chestFramePos);
35041 baseFrame->setSize(chestBasePos);
35042
35043 // align buttons
35044 auto grabAllBtnPos = grabAllBtn->getSize();
35045 auto bg = baseFrame->findImage("chest base img");
35046 grabAllBtnPos.y = bg->pos.y + bg->pos.h - 4 - grabAllBtnPos.h - 4;
35047 grabAllBtnPos.x = bg->pos.x + bg->pos.w / 2 - grabAllBtnPos.w / 2;
35048 grabAllBtn->setSize(grabAllBtnPos);
35049
35050 auto closeBtnPos = closeBtn->getSize();
35051 closeBtnPos.x = bg->pos.x + bg->pos.w - closeBtnPos.w - 8 - 4;
35052 closeBtnPos.y = titlePos.y + titlePos.h / 2 - closeBtnPos.h / 2;
35053 closeBtn->setSize(closeBtnPos);
35054
35055
35056 if ( player.GUI.bModuleAccessibleWithMouse(Player::GUI_t::MODULE_CHEST)
35057 && inputs.getVirtualMouse(player.playernum)->draw_cursor && !drawGlyphs )
35058 {
35059 if ( !grabAllBtn->isDisabled() && grabAllBtn->isHighlighted() )
35060 {
35061 player.GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_CHEST);
35062 if ( player.GUI.activeModule != Player::GUI_t::MODULE_CHEST )
35063 {
35064 player.GUI.activateModule(Player::GUI_t::MODULE_CHEST);
35065 }
35066 SDL_Rect pos = grabAllBtn->getAbsoluteSize();
35067 // make sure to adjust absolute size to camera viewport
35068 pos.x -= player.camera_virtualx1();
35069 pos.y -= player.camera_virtualy1();
35070 player.hud.setCursorDisabled(false);
35071 player.hud.updateCursorAnimation(pos.x - 1, pos.y - 1, pos.w, pos.h, inputs.getVirtualMouse(player.playernum)->draw_cursor);
35072 }
35073 if ( !closeBtn->isDisabled() && closeBtn->isHighlighted() )
35074 {
35075 player.GUI.setHoveringOverModuleButton(Player::GUI_t::MODULE_CHEST);
35076 if ( player.GUI.activeModule != Player::GUI_t::MODULE_CHEST )
35077 {
35078 player.GUI.activateModule(Player::GUI_t::MODULE_CHEST);
35079 }
35080 SDL_Rect pos = closeBtn->getAbsoluteSize();
35081 // make sure to adjust absolute size to camera viewport
35082 pos.x -= player.camera_virtualx1();
35083 pos.y -= player.camera_virtualy1();
35084 player.hud.setCursorDisabled(false);
35085 player.hud.updateCursorAnimation(pos.x - 1, pos.y - 1, pos.w, pos.h, inputs.getVirtualMouse(player.playernum)->draw_cursor);
35086 }
35087 }
35088
35089 int furthestLeftPrompt = xres;
35090 if ( !promptBack->isDisabled() )
35091 {
35092 promptBack->setText(Language::get(4053));
35093 SDL_Rect glyphPos = promptBackImg->pos;
35094 if ( auto textGet = Text::get(promptBack->getText(), promptBack->getFont(),
35095 promptBack->getTextColor(), promptBack->getOutlineColor()) )
35096 {
35097 SDL_Rect textPos = promptBack->getSize();
35098 textPos.w = textGet->getWidth();
35099 textPos.x = bg->pos.x + bg->pos.w - textPos.w - 8;
35100 textPos.y = bg->pos.y + bg->pos.h - textPos.h + 8 + 8;
35101 promptBack->setSize(textPos);
35102 glyphPos.x = promptBack->getSize().x + promptBack->getSize().w - textGet->getWidth() - 4;
35103 }
35104 promptBackImg->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuCancel");
35105 Image* glyphImage = Image::get(promptBackImg->path.c_str());
35106 if ( glyphImage )
35107 {
35108 glyphPos.w = glyphImage->getWidth();
35109 glyphPos.h = glyphImage->getHeight();
35110 glyphPos.x -= glyphPos.w;
35111 furthestLeftPrompt = std::min(glyphPos.x + glyphPos.w / 2, furthestLeftPrompt);
35112 glyphPos.y = promptBack->getSize().y + promptBack->getSize().h / 2 - glyphPos.h / 2;
35113 promptBackImg->pos = glyphPos;
35114 }
35115 }
35116 else if ( drawGlyphs )
35117 {
35118 SDL_Rect glyphPos = closeBtn->getSize();
35119 //glyphPos.x += glyphPos.w / 2;
35120 promptBackImg->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuCancel");
35121 promptBackImg->ontop = true;
35122 Image* glyphImage = Image::get(promptBackImg->path.c_str());
35123 if ( glyphImage )
35124 {
35125 glyphPos.w = glyphImage->getWidth();
35126 glyphPos.h = glyphImage->getHeight();
35127 glyphPos.x -= glyphPos.w;
35128 glyphPos.x += 2;
35129 glyphPos.y += 2;
35130 promptBackImg->pos = glyphPos;
35131 }
35132 }
35133 if ( !promptGrab->isDisabled() )
35134 {
35135 promptGrab->setText(Language::get(4091));
35136 auto promptGrabImg = baseFrame->findImage("prompt grab img");
35137 SDL_Rect glyphPos = promptGrabImg->pos;
35138 if ( auto textGet = Text::get(promptGrab->getText(), promptGrab->getFont(),
35139 promptGrab->getTextColor(), promptGrab->getOutlineColor()) )
35140 {
35141 SDL_Rect textPos = promptGrab->getSize();
35142 textPos.w = textGet->getWidth();
35143 textPos.x = bg->pos.x + bg->pos.w - textPos.w - 8;
35144 textPos.y = promptBack->getSize().y - textPos.h + 2;
35145 promptGrab->setSize(textPos);
35146 glyphPos.x = promptGrab->getSize().x + promptGrab->getSize().w - textGet->getWidth() - 4;
35147 }
35148 promptGrabImg->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuPageRightAlt");
35149 Image* glyphImage = Image::get(promptGrabImg->path.c_str());
35150 if ( glyphImage )
35151 {
35152 glyphPos.w = glyphImage->getWidth();
35153 glyphPos.h = glyphImage->getHeight();
35154 glyphPos.x -= glyphPos.w;
35155 furthestLeftPrompt = std::min(glyphPos.x + glyphPos.w / 2, furthestLeftPrompt);
35156 glyphPos.y = promptGrab->getSize().y + promptGrab->getSize().h / 2 - glyphPos.h / 2;
35157 promptGrabImg->pos = glyphPos;
35158
35159 if ( furthestLeftPrompt < promptGrabImg->pos.x + promptGrabImg->pos.w / 2 )
35160 {
35161 int offset = (promptGrabImg->pos.x + promptGrabImg->pos.w / 2) - furthestLeftPrompt;
35162 promptGrabImg->pos.x -= offset;
35163 auto pos = promptGrab->getSize();
35164 pos.x -= offset;
35165 promptGrab->setSize(pos);
35166 }
35167 }
35168 }
35169 else if ( drawGlyphs )
35170 {
35171 SDL_Rect glyphPos = grabAllBtn->getSize();
35172 glyphPos.x += glyphPos.w / 2;
35173 glyphPos.y += glyphPos.h - 4;
35174 promptGrabImg->path = Input::inputs[player.playernum].getGlyphPathForBinding("MenuPageRightAlt");
35175 promptGrabImg->ontop = true;
35176 Image* glyphImage = Image::get(promptGrabImg->path.c_str());
35177 if ( glyphImage )
35178 {
35179 glyphPos.w = glyphImage->getWidth();
35180 glyphPos.h = glyphImage->getHeight();
35181 glyphPos.x -= glyphPos.w / 2;
35182 promptGrabImg->pos = glyphPos;
35183 }
35184 }
35185 if ( !promptBack->isDisabled() )
35186 {
35187 if ( furthestLeftPrompt < promptBackImg->pos.x + promptBackImg->pos.w / 2 )
35188 {
35189 int offset = (promptBackImg->pos.x + promptBackImg->pos.w / 2) - furthestLeftPrompt;
35190 promptBackImg->pos.x -= offset;
35191 auto pos = promptBack->getSize();
35192 pos.x = promptGrab->getSize().x;
35193 promptBack->setSize(pos);
35194 }
35195 }
35196 }
35197
35198 bool closeChestAction = false;
35199 if ( !inputs.getUIInteraction(player.playernum)->selectedItem
35200 && player.GUI.bModuleAccessibleWithMouse(Player::GUI_t::MODULE_CHEST)
35201 && !player.GUI.isDropdownActive()
35202 && !player.inventoryUI.spellPanel.bOpen
35203 && player.bControlEnabled && !gamePaused && !player.usingCommand() )
35204 {
35205 if ( openedChest[player.playernum] || bOpen )
35206 {
35207 if ( Input::inputs[player.playernum].binaryToggle("MenuCancel") )
35208 {
35209 Input::inputs[player.playernum].consumeBinaryToggle("MenuCancel");
35210 closeChestAction = true;
35211 }
35212 else if ( !promptGrabImg->disabled && Input::inputs[player.playernum].consumeBinaryToggle("MenuPageRightAlt") )
35213 {
35214 takeAllChestGUIAction(player.playernum);
35215 Player::soundActivate();
35216 }
35217 }
35218 }
35219
35220 SDL_Rect actualSize = chestSlotsFrame->getActualSize();
35221 actualSize.y = scrollAnimateX;
35222 chestSlotsFrame->setActualSize(actualSize);
35223
35224 if ( closeChestAction )
35225 {
35226 closeChestGUIAction(player.playernum);
35227 return;
35228 }
35229}
35230
35231bool Player::Inventory_t::ChestGUI_t::isSlotVisible(int x, int y) const
35232{
35233 if ( player.inventoryUI.chestFrame )
35234 {
35235 if ( player.inventoryUI.chestFrame->isDisabled() )
35236 {
35237 return false;
35238 }
35239 }
35240 int lowerY = currentScrollRow;
35241 int upperY = currentScrollRow + getNumItemsToDisplayVertical() - 1;
35242
35243 if ( y >= lowerY && y <= upperY )
35244 {
35245 return true;
35246 }
35247 return false;
35248}
35249
35250bool Player::Inventory_t::ChestGUI_t::isItemVisible(Item* item) const
35251{
35252 if ( !item ) { return false; }
35253 return isSlotVisible(item->x, item->y);
35254}
35255
35256void Player::Inventory_t::ChestGUI_t::scrollToSlot(int x, int y, bool instantly)
35257{
35258 int lowerY = currentScrollRow;
35259 int upperY = currentScrollRow + getNumItemsToDisplayVertical() - 1;
35260
35261 if ( y >= lowerY && y <= upperY )
35262 {
35263 // no work to do.
35264 return;
35265 }
35266
35267 int lowestItemY = getNumItemsToDisplayVertical() - 1;
35268 for ( node_t* node = stats[player.playernum]->inventory.first; node != NULL__null; node = node->next )
35269 {
35270 Item* item = (Item*)node->element;
35271 if ( !item ) { continue; }
35272 if ( itemCategory(item) != SPELL_CAT ) { continue; }
35273
35274 lowestItemY = std::max(lowestItemY, item->y);
35275 }
35276 int maxScroll = std::max((lowestItemY + 1) - (getNumItemsToDisplayVertical()), 0) * player.inventoryUI.getSlotSize();
35277
35278 int scrollAmount = 0;
35279 if ( y < lowerY )
35280 {
35281 scrollAmount = (y)* player.inventoryUI.getSlotSize();
35282 //scrollAmount += scrollSetpoint;
35283 }
35284 else if ( y > upperY )
35285 {
35286 scrollAmount = (y - upperY) * player.inventoryUI.getSlotSize();
35287 scrollAmount += scrollSetpoint;
35288 }
35289 scrollAmount = std::min(scrollAmount, maxScroll);
35290
35291 scrollSetpoint = scrollAmount;
35292 if ( instantly )
35293 {
35294 scrollAnimateX = scrollSetpoint;
35295 }
35296 currentScrollRow = scrollSetpoint / player.inventoryUI.getSlotSize();
35297 if ( abs(scrollSetpoint - scrollAnimateX) > 0.00001 )
35298 {
35299 isInteractable = false;
35300 }
35301}
35302
35303void MinotaurWarning_t::deinit()
35304{
35305 started = false;
35306 state = 0;
35307 stateInit = 0;
35308 animFade = 0.0;
35309 animTicks = 0;
35310 animBg = 0.0;
35311 initialWarningCompleted = false;
35312 minotaurSpawned = false;
35313 minotaurDied = false;
35314 minotaurUid = 0;
35315 animFlashIncrease = true;
35316 animFlash = 0.0;
35317}
35318
35319void MinotaurWarning_t::init()
35320{
35321 deinit();
35322 started = true;
35323}
35324
35325void MinotaurWarning_t::setAnimatePosition(const int destx, const int desty, const int destw, const int desth)
35326{
35327 animateStartX = pos.x;
35328 animateStartY = pos.y;
35329 animateStartW = pos.w;
35330 animateStartH = pos.h;
35331 animateSetpointX = destx;
35332 animateSetpointY = desty;
35333 animateSetpointW = destw;
35334 animateSetpointH = desth;
35335 animateX = 0.0;
35336 animateY = 0.0;
35337 animateW = 0.0;
35338 animateH = 0.0;
35339}
35340
35341void MinotaurWarning_t::setAnimatePosition(int destx, int desty)
35342{
35343 animateStartX = pos.x;
35344 animateStartY = pos.y;
35345 animateStartW = pos.w;
35346 animateStartH = pos.h;
35347 animateSetpointX = destx;
35348 animateSetpointY = desty;
35349 animateSetpointW = 0;
35350 animateSetpointH = 0;
35351 animateX = 0.0;
35352 animateY = 0.0;
35353 animateW = 0.0;
35354 animateH = 0.0;
35355}
35356
35357void Player::HUD_t::updateMinotaurWarning()
35358{
35359 auto& m = minotaurWarning[player.playernum];
35360
35361 if ( gamePaused && multiplayer == SINGLE0 )
35362 {
35363 if ( minotaurFrame )
35364 {
35365 minotaurFrame->setDisabled(true);
35366 }
35367 return;
35368 }
35369
35370 static ConsoleVariable<bool> cvar_minoanimdebug("/minoanimdebug", false);
35371 if ( *cvar_minoanimdebug && player.playernum == clientnum )
35372 {
35373 *cvar_minoanimdebug = false;
35374 m.init();
35375 }
35376
35377 static ConsoleVariable<float> cvar_minoflashmax("/minoflashmax", 1.25);
35378 static ConsoleVariable<float> cvar_minoflashmin("/minoflashmin", 0.25);
35379 static ConsoleVariable<float> cvar_minoflashrate("/minoflashrate", 0.125);
35380
35381 if ( m.processedOnTick == 0 )
35382 {
35383 m.processedOnTick = ticks;
35384 }
35385 else if ( m.processedOnTick != ticks )
35386 {
35387 m.processedOnTick = ticks;
35388 ++m.animTicks;
35389 }
35390
35391 bool newLevel = m.levelProcessed != currentlevel;
35392 if ( !minotaurlevel || (newLevel && m.started) )
35393 {
35394 m.deinit();
35395 }
35396 m.levelProcessed = currentlevel;
35397
35398 if ( !m.started )
35399 {
35400 if ( minotaurFrame )
35401 {
35402 minotaurFrame->setDisabled(true);
35403 }
35404 }
35405 if ( minotaurDisplay )
35406 {
35407 minotaurDisplay->setDisabled(true);
35408 }
35409 if ( player.playernum == 0 && minotaurSharedDisplay )
35410 {
35411 minotaurSharedDisplay->setDisabled(true);
35412 }
35413
35414 if ( !player.isLocalPlayer() )
35415 {
35416 m.deinit();
35417 return;
35418 }
35419
35420 if ( minotaurlevel && m.animTicks >= 50 && !m.started )
35421 {
35422 m.init();
35423 }
35424
35425 if ( !m.started )
35426 {
35427 return;
35428 }
35429
35430 if ( !minotaurFrame )
35431 {
35432 minotaurFrame = gameUIFrame[player.playernum]->addFrame("minotaur");
35433 minotaurFrame->setInheritParentFrameOpacity(false);
35434 minotaurFrame->setHollow(true);
35435
35436 Frame::image_t* img = minotaurFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0,
35437 "*images/system/white.png", "mino dimmer");
35438 img->disabled = true;
35439
35440 img = minotaurFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
35441 "*images/ui/HUD/HUD_Minotaur_00.png", "mino img bg");
35442 img->disabled = true;
35443
35444 img = minotaurFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF,
35445 "*images/ui/HUD/HUD_Minotaur_00.png", "mino img fg");
35446 img->disabled = true;
35447 }
35448
35449 minotaurFrame->setSize(SDL_Rect{
35450 player.camera_virtualx1(),
35451 player.camera_virtualy1(),
35452 player.camera_virtualWidth(),
35453 player.camera_virtualHeight() });
35454
35455 auto dimmer = minotaurFrame->findImage("mino dimmer");
35456 dimmer->pos = SDL_Rect{ 0, 0, minotaurFrame->getSize().w, minotaurFrame->getSize().h };
35457 dimmer->disabled = false;
35458 auto minotaurImgBg = minotaurFrame->findImage("mino img bg");
35459 minotaurImgBg->disabled = true;
35460 auto minotaurImgFg = minotaurFrame->findImage("mino img fg");
35461 minotaurImgFg->disabled = true;
35462 minotaurFrame->setDisabled(player.hud.hudFrame->isDisabled());
35463 int imgSizeX = 60;
35464 int imgSizeY = 66;
35465 if ( auto imgGet = Image::get(minotaurImgBg->path.c_str()) )
35466 {
35467 imgSizeX = imgGet->getWidth();
35468 imgSizeY = imgGet->getHeight();
35469 }
35470 const int midX = dimmer->pos.w / 2;
35471 const int midY = dimmer->pos.h / 2;
35472
35473 std::string minimapFrameName = "minimap";
35474 minimapFrameName.append(std::to_string(player.playernum));
35475 SDL_Rect sharedminimapPos{ 0, 0, 0, 0 };
35476 SDL_Rect minimapPos{ 0, 0, 0, 0 };
35477 Frame* minimap = nullptr;
35478 bool leftAlignToMap = splitscreen
35479 && player.bUseCompactGUIHeight()
35480 && !player.bUseCompactGUIWidth();
35481 int leftAlignYOffset = 16;
35482 int leftAlignXOffset = 0;
35483 int topAlignYOffset = 0;
35484 if ( leftAlignToMap && *MainMenu::clipped_splitscreen && player.hud.actionPromptsFrame && !player.hud.actionPromptsFrame->isDisabled() )
35485 {
35486 leftAlignYOffset += 16;
35487 leftAlignXOffset += 8;
35488 }
35489 else if ( !leftAlignToMap && *MainMenu::clipped_splitscreen && !player.bUseCompactGUIHeight() && player.bUseCompactGUIWidth() )
35490 {
35491 topAlignYOffset = -8;
35492 }
35493 if ( minimap = player.hud.hudFrame->findFrame(minimapFrameName.c_str()) )
35494 {
35495 minimapPos.x = minotaurFrame->getSize().w - player.minimap.minimapPos.w;
35496 minimapPos.y = minotaurFrame->getSize().h - player.minimap.minimapPos.h;
35497 int mapHeightOffset = 0;
35498 if ( player.hud.mapPromptFrame && !player.hud.mapPromptFrame->isDisabled() )
35499 {
35500 mapHeightOffset = player.hud.mapPromptFrame->getSize().h;
35501 }
35502 else if ( player.hud.gameTimerFrame && !player.hud.gameTimerFrame->isDisabled()
35503 // ignore if ui raised above hotbar and vertical split layout
35504 && (!(player.hud.offsetHUDAboveHotbarHeight > 0 && splitscreen && !player.bUseCompactGUIHeight() && player.bUseCompactGUIWidth())) )
35505 {
35506 mapHeightOffset = player.hud.gameTimerFrame->getSize().h;
35507 }
35508 else if ( player.hud.actionPromptsFrame && !player.hud.actionPromptsFrame->isDisabled() )
35509 {
35510 leftAlignYOffset += 44;
35511 }
35512 mapHeightOffset += player.hud.offsetHUDAboveHotbarHeight;
35513 minimapPos.y -= mapHeightOffset;
35514 }
35515 minimapPos.w = player.minimap.minimapPos.w;
35516 minimapPos.h = player.minimap.minimapPos.h;
35517 if ( ::minimapFrame )
35518 {
35519 sharedminimapPos = Player::Minimap_t::sharedMinimapPos;
35520 }
35521
35522 {
35523 Uint8 r, g, b, a;
35524 getColor(dimmer->color, &r, &g, &b, &a);
35525 a = m.animFade * 64;
35526 dimmer->color = makeColor(r, g, b, a);
35527
35528 if ( m.state >= 8 || m.initialWarningCompleted )
35529 {
35530 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
35531 real_t setpointDiff = fpsScale * std::max(.01, (m.animFade)) / 5.0;
35532 m.animFade -= setpointDiff;
35533 m.animFade = std::max(0.0, m.animFade);
35534 }
35535 else
35536 {
35537 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
35538 real_t setpointDiff = fpsScale * std::max(.01, (1.0 - m.animFade)) / 5.0;
35539 m.animFade += setpointDiff;
35540 m.animFade = std::min(1.0, m.animFade);
35541 }
35542 }
35543
35544 static ConsoleVariable<float> cvar_minoanimspeed("/minoanimspeed", 1.0);
35545 static ConsoleVariable<int> cvar_minoanimvolume("/minoanimvolume", 128);
35546 real_t animspeed = 5.0 * (*cvar_minoanimspeed);//5.0 / 4.0;
35547 const int movementAmount = 0;
35548 real_t scalingAmount = 2.0;
35549 real_t midScalingAmount = 1.5;
35550
35551 if ( m.state == 0 )
35552 {
35553 if ( m.stateInit == 0 )
35554 {
35555 m.pos = SDL_Rect{ midX - imgSizeX / 2, midY - imgSizeY / 2, imgSizeX, imgSizeY };
35556 m.stateInit = 1;
35557 m.setAnimatePosition(
35558 midX - imgSizeX / 2 - movementAmount,
35559 midY - imgSizeY / 2 - movementAmount,
35560 imgSizeX, imgSizeY);
35561 }
35562 minotaurImgFg->color = makeColor(255, 255, 255, 255 * m.animateX);
35563 minotaurImgFg->disabled = false;
35564 if ( m.animateX >= 1.0 )
35565 {
35566 m.animTicks = 0;
35567 m.state = 1;
35568 }
35569 animspeed *= 2;
35570 }
35571 else if ( m.state == 1 )
35572 {
35573 if ( m.stateInit == 1 )
35574 {
35575 m.stateInit = 2;
35576 m.setAnimatePosition(
35577 midX - imgSizeX / 2 - movementAmount - (scalingAmount - 1.0) * imgSizeX / 2,
35578 midY - imgSizeY / 2 - movementAmount - (scalingAmount - 1.0) * imgSizeY / 2,
35579 imgSizeX * scalingAmount,
35580 imgSizeY * scalingAmount);
35581 }
35582 minotaurImgFg->color = 0xFFFFFFFF;
35583 minotaurImgFg->disabled = false;
35584 if ( m.animateX >= 1.0 )
35585 {
35586 m.animTicks = 0;
35587 m.state = 2;
35588 }
35589 animspeed /= 2;
35590 }
35591 else if ( m.state == 2 )
35592 {
35593 if ( m.stateInit == 2 )
35594 {
35595 m.stateInit = 3;
35596 m.setAnimatePosition(
35597 midX - imgSizeX / 2 - movementAmount - (midScalingAmount - 1.0) * imgSizeX / 2,
35598 midY - imgSizeY / 2 - movementAmount - (midScalingAmount - 1.0) * imgSizeY / 2,
35599 imgSizeX * midScalingAmount,
35600 imgSizeY * midScalingAmount);
35601 m.animBg = 1.0;
35602 if ( !splitscreen || (splitscreen && player.playernum == 0) )
35603 {
35604 playSound(514, *cvar_minoanimvolume);
35605 }
35606 }
35607 minotaurImgFg->disabled = false;
35608
35609 minotaurImgBg->pos = SDL_Rect{ midX - imgSizeX,
35610 midY - imgSizeY, imgSizeX * 2, imgSizeY * 2 };
35611 if ( m.animateX >= 1.0 )
35612 {
35613 m.animTicks = 0;
35614 m.state = 3;
35615 }
35616 }
35617 else if ( m.state == 3 )
35618 {
35619 if ( m.stateInit == 3 )
35620 {
35621 m.stateInit = 4;
35622 m.setAnimatePosition(
35623 midX - imgSizeX / 2 - movementAmount - (midScalingAmount - 1.0) * imgSizeX / 2,
35624 midY - imgSizeY / 2 - movementAmount - (midScalingAmount - 1.0) * imgSizeY / 2,
35625 imgSizeX * midScalingAmount,
35626 imgSizeY * midScalingAmount);
35627 }
35628 minotaurImgFg->disabled = false;
35629
35630 if ( m.animateX >= 1.0 )
35631 {
35632 m.animTicks = 0;
35633 m.state = 4;
35634 }
35635 }
35636 else if ( m.state == 4 )
35637 {
35638 if ( m.stateInit == 4 )
35639 {
35640 m.stateInit = 5;
35641 m.setAnimatePosition(
35642 midX - imgSizeX / 2 - movementAmount,
35643 midY - imgSizeY / 2 - movementAmount,
35644 imgSizeX, imgSizeY);
35645 }
35646 minotaurImgFg->disabled = false;
35647
35648 if ( m.animateX >= 1.0 )
35649 {
35650 m.animTicks = 0;
35651 m.state = 5;
35652 }
35653 animspeed /= 2;
35654 }
35655 else if ( m.state == 5 )
35656 {
35657 if ( m.stateInit == 5 )
35658 {
35659 m.stateInit = 6;
35660 m.setAnimatePosition(
35661 midX - imgSizeX / 2 - movementAmount - (scalingAmount - 1.0) * imgSizeX / 2,
35662 midY - imgSizeY / 2 - movementAmount - (scalingAmount - 1.0) * imgSizeY / 2,
35663 imgSizeX * scalingAmount,
35664 imgSizeY * scalingAmount);
35665 }
35666 minotaurImgFg->color = 0xFFFFFFFF;
35667 minotaurImgFg->disabled = false;
35668 if ( m.animateX >= 1.0 )
35669 {
35670 m.animTicks = 0;
35671 m.state = 6;
35672 }
35673 animspeed /= 2;
35674 }
35675 else if ( m.state == 6 )
35676 {
35677 if ( m.stateInit == 6 )
35678 {
35679 m.stateInit = 7;
35680 m.setAnimatePosition(
35681 midX - imgSizeX / 2 - movementAmount - (midScalingAmount - 1.0) * imgSizeX / 2,
35682 midY - imgSizeY / 2 - movementAmount - (midScalingAmount - 1.0) * imgSizeY / 2,
35683 imgSizeX * midScalingAmount,
35684 imgSizeY * midScalingAmount);
35685 m.animBg = 1.0;
35686 if ( !splitscreen || (splitscreen && player.playernum == 0) )
35687 {
35688 playSound(515, *cvar_minoanimvolume);
35689 }
35690 }
35691 minotaurImgFg->disabled = false;
35692
35693 if ( m.animateX >= 1.0 )
35694 {
35695 m.animTicks = 0;
35696 m.state = 7;
35697 }
35698 }
35699 else if ( m.state == 7 )
35700 {
35701 if ( m.stateInit == 7 )
35702 {
35703 m.stateInit = 8;
35704 m.setAnimatePosition(
35705 midX - imgSizeX / 2 - movementAmount - (midScalingAmount - 1.0) * imgSizeX / 2,
35706 midY - imgSizeY / 2 - movementAmount - (midScalingAmount - 1.0) * imgSizeY / 2,
35707 imgSizeX * midScalingAmount,
35708 imgSizeY * midScalingAmount);
35709 }
35710 minotaurImgFg->disabled = false;
35711
35712 if ( m.animateX >= 1.0 )
35713 {
35714 m.animTicks = 0;
35715 m.state = 8;
35716 }
35717 }
35718 else if ( m.state == 8 )
35719 {
35720 if ( m.stateInit == 8 )
35721 {
35722 m.stateInit = 9;
35723 m.setAnimatePosition(
35724 midX - imgSizeX / 2 - movementAmount,
35725 midY - imgSizeY / 2 - movementAmount,
35726 imgSizeX, imgSizeY);
35727 }
35728 minotaurImgFg->color = makeColor(255, 255, 255, 255);
35729 minotaurImgFg->disabled = false;
35730
35731 if ( m.animateX >= 1.0 )
35732 {
35733 m.animTicks = 0;
35734 m.state = 9;
35735 }
35736 animspeed /= 2;
35737 }
35738 else if ( m.state == 9 )
35739 {
35740 if ( m.stateInit == 9 )
35741 {
35742 m.stateInit = 10;
35743 if ( ::minimapFrame && !::minimapFrame->isInvisible() && sharedminimapPos.x > 0 )
35744 {
35745 if ( minotaurSharedDisplay )
35746 {
35747 /*m.setAnimatePosition(
35748 sharedminimapPos.x + sharedminimapPos.w / 2 - imgSizeX / 2 - movementAmount,
35749 sharedminimapPos.y - imgSizeY - 16 - movementAmount,
35750 imgSizeX, imgSizeY);*/
35751 }
35752 m.setAnimatePosition(
35753 midX - imgSizeX / 2 - movementAmount,
35754 midY - imgSizeY / 2 - movementAmount,
35755 imgSizeX, imgSizeY);
35756 }
35757 else if ( minotaurDisplay && minimap && !minimap->isInvisible() && minimapPos.x > 0 )
35758 {
35759 if ( leftAlignToMap )
35760 {
35761 m.setAnimatePosition(
35762 minimapPos.x - imgSizeX - 16 + leftAlignXOffset - movementAmount,
35763 minimapPos.y + minimapPos.h - imgSizeY - leftAlignYOffset - movementAmount,
35764 imgSizeX, imgSizeY);
35765 }
35766 else
35767 {
35768 m.setAnimatePosition(
35769 minimapPos.x + minimapPos.w - imgSizeX - 16 - movementAmount,
35770 minimapPos.y - imgSizeY - 16 - topAlignYOffset - movementAmount,
35771 imgSizeX, imgSizeY);
35772 }
35773 }
35774 else
35775 {
35776 m.setAnimatePosition(
35777 minotaurFrame->getSize().w - imgSizeX - 16 - movementAmount,
35778 minotaurFrame->getSize().h - imgSizeY - 16 - movementAmount,
35779 imgSizeX, imgSizeY);
35780 }
35781 }
35782
35783 minotaurImgFg->color = makeColor(255, 255, 255, 255);
35784 if ( ::minimapFrame && !::minimapFrame->isInvisible() )
35785 {
35786 /*if ( !minotaurSharedDisplay )*/
35787 {
35788 // fade out icon for local splitscreen
35789 minotaurImgFg->color = makeColor(255, 255, 255, 255 * (1.0 - m.animateX));
35790 }
35791 }
35792
35793 minotaurImgFg->disabled = false;
35794
35795 if ( m.animateX >= 1.0 )
35796 {
35797 m.animTicks = 0;
35798 m.state = 10;
35799 m.animFlash = *cvar_minoflashmax * 2;
35800 m.animFlashIncrease = false;
35801 /*if ( !splitscreen || (splitscreen && player.playernum == 0) )
35802 {
35803 playSound(517, 64);
35804 }*/
35805 }
35806 }
35807 else if ( m.state == 10 )
35808 {
35809 m.initialWarningCompleted = true;
35810 }
35811
35812 if ( m.initialWarningCompleted )
35813 {
35814 if ( !m.minotaurDied && ticks % 5 == 0 && (!splitscreen || splitscreen && player.playernum == 0) )
35815 {
35816 if ( !m.minotaurSpawned )
35817 {
35818 for ( node_t* mapNode = map.creatures->first; mapNode != nullptr; mapNode = mapNode->next )
35819 {
35820 Entity* monster = (Entity*)mapNode->element;
35821 if ( monster && monster->getMonsterTypeFromSprite() == MINOTAUR )
35822 {
35823 m.minotaurSpawned = true;
35824 m.minotaurUid = monster->getUID();
35825 }
35826 }
35827 }
35828 else if ( !uidToEntity(m.minotaurUid) )
35829 {
35830 m.minotaurDied = true;
35831 }
35832 }
35833
35834 if ( splitscreen && player.playernum != 0 )
35835 {
35836 m.minotaurDied = minotaurWarning[0].minotaurDied;
35837 m.minotaurSpawned = minotaurWarning[0].minotaurSpawned;
35838 }
35839
35840 bool flash = false;
35841 if ( m.minotaurSpawned && !m.minotaurDied )
35842 {
35843 m.animFlash = 1.0;
35844 flash = !((ticks % 50) - (ticks % 25));
35845 }
35846 else if ( m.animFlashIncrease )
35847 {
35848 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
35849 real_t setpointDiff = fpsScale * (*cvar_minoflashrate) / 5.0;
35850 m.animFlash += setpointDiff;
35851 if ( m.animFlash >= *cvar_minoflashmax )
35852 {
35853 m.animFlashIncrease = false;
35854 }
35855 m.animFlash = std::min(*cvar_minoflashmax, (float)m.animFlash);
35856 }
35857 else
35858 {
35859 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
35860 real_t setpointDiff = fpsScale * (*cvar_minoflashrate) / 5.0;
35861 m.animFlash -= setpointDiff;
35862 if ( m.animFlash <= *cvar_minoflashmin )
35863 {
35864 m.animFlashIncrease = true;
35865 }
35866 m.animFlash = std::max(*cvar_minoflashmin, (float)m.animFlash);
35867 }
35868
35869 if ( m.minotaurDied )
35870 {
35871 // don't show icon.
35872 }
35873 else if ( ::minimapFrame && !::minimapFrame->isInvisible() && sharedminimapPos.x > 0 )
35874 {
35875 if ( minotaurSharedDisplay )
35876 {
35877 minotaurSharedDisplay->setDisabled(flash);
35878 minotaurSharedDisplay->setOpacity(100.0 * std::max(0.25, std::min(1.0, m.animFlash)));
35879 minotaurSharedDisplay->setSize(SDL_Rect{ sharedminimapPos.x + sharedminimapPos.w / 2 - imgSizeX / 2 - movementAmount,
35880 sharedminimapPos.y - imgSizeY - 16 - movementAmount,
35881 imgSizeX, imgSizeY });
35882 /*if ( (minotaurSharedDisplay->getSize().x + minotaurSharedDisplay->getSize().w) >
35883 (players[0]->camera_virtualx1() + players[0]->camera_virtualWidth()) )*/
35884 {
35885 // keep on player 1's camera for now.
35886 SDL_Rect pos = minotaurSharedDisplay->getSize();
35887 //int diff = (players[0]->camera_virtualx1() + players[0]->camera_virtualWidth()) -
35888 // (minotaurSharedDisplay->getSize().x + minotaurSharedDisplay->getSize().w);
35889 //pos.x -= abs(diff) + 4;
35890 pos.x = players[0]->camera_virtualx1() + players[0]->camera_virtualWidth() - pos.w - 4;
35891 pos.y = players[0]->camera_virtualy1() + players[0]->camera_virtualHeight() - pos.h - 4;
35892 if ( players[0]->hud.mapPromptFrame && !players[0]->hud.mapPromptFrame->isDisabled() )
35893 {
35894 pos.y -= players[0]->hud.mapPromptFrame->getSize().h;
35895 }
35896 minotaurSharedDisplay->setSize(pos);
35897 }
35898 }
35899 }
35900 else if ( minotaurDisplay && minimap && !minimap->isInvisible() && minimapPos.x > 0 )
35901 {
35902 minotaurDisplay->setDisabled(flash);
35903 minotaurDisplay->setOpacity(100.0 * std::max(0.25, std::min(1.0, m.animFlash)));
35904
35905 if ( leftAlignToMap )
35906 {
35907 minotaurDisplay->setSize(SDL_Rect{
35908 minimapPos.x - imgSizeX - 16 + leftAlignXOffset - movementAmount,
35909 minimapPos.y + minimapPos.h - imgSizeY - leftAlignYOffset - movementAmount,
35910 imgSizeX, imgSizeY });
35911 }
35912 else
35913 {
35914 minotaurDisplay->setSize(SDL_Rect{ minimapPos.x + minimapPos.w - imgSizeX - 16 - movementAmount,
35915 minimapPos.y - imgSizeY - 16 - topAlignYOffset - movementAmount,
35916 imgSizeX, imgSizeY });
35917 }
35918 }
35919 else
35920 {
35921 minotaurDisplay->setDisabled(flash);
35922 minotaurDisplay->setOpacity(100.0 * std::max(0.25, std::min(1.0, m.animFlash)));
35923 minotaurDisplay->setSize(SDL_Rect{ minotaurFrame->getSize().w - imgSizeX - 16 - movementAmount,
35924 minotaurFrame->getSize().h - imgSizeY - 16 - movementAmount,
35925 imgSizeX, imgSizeY });
35926 }
35927 }
35928
35929 if ( m.animBg > 0.0 )
35930 {
35931 minotaurImgBg->color = makeColor(255, 255, 255, 255 * (std::min(1.0, m.animBg)));
35932 minotaurImgBg->disabled = false;
35933 }
35934
35935 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
35936 real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - m.animateX)) / (animspeed);
35937 real_t setpointDiffY = fpsScale * std::max(.1, (1.0 - m.animateY)) / (animspeed);
35938 real_t setpointDiffW = fpsScale * std::max(.1, (1.0 - m.animateW)) / (animspeed);
35939 real_t setpointDiffH = fpsScale * std::max(.1, (1.0 - m.animateH)) / (animspeed);
35940 m.animateX += setpointDiffX;
35941 m.animateY += setpointDiffY;
35942 m.animateX = std::min(1.0, m.animateX);
35943 m.animateY = std::min(1.0, m.animateY);
35944 m.animateW += setpointDiffW;
35945 m.animateH += setpointDiffH;
35946 m.animateW = std::min(1.0, m.animateW);
35947 m.animateH = std::min(1.0, m.animateH);
35948
35949 int destX = m.animateSetpointX - m.animateStartX;
35950 int destY = m.animateSetpointY - m.animateStartY;
35951 int destW = m.animateSetpointW - m.animateStartW;
35952 int destH = m.animateSetpointH - m.animateStartH;
35953
35954 m.pos.x = m.animateStartX + destX * m.animateX;
35955 m.pos.y = m.animateStartY + destY * m.animateY;
35956 m.pos.w = m.animateStartW + destW * m.animateW;
35957 m.pos.h = m.animateStartH + destH * m.animateH;
35958 minotaurImgFg->pos = m.pos;
35959
35960 m.animBg -= fpsScale * 0.2 / animspeed;
35961 m.animBg = std::max(0.0, m.animBg);
35962}
35963
35964bool Player::WorldUI_t::WorldTooltipItem_t::isItemSameAsCurrent(Item* item)
35965{
35966 if ( !item )
35967 {
35968 return false;
35969 }
35970 if ( item->type == type
35971 && item->status == status
35972 && item->beatitude == beatitude
35973 && item->count == count
35974 && item->appearance == appearance
35975 && item->identified == identified )
35976 {
35977 return true;
35978 }
35979 return false;
35980}
35981
35982
35983SDL_Surface* Player::WorldUI_t::WorldTooltipItem_t::blitItemWorldTooltip(Item* item)
35984{
35985 if ( !item )
35986 {
35987 return nullptr;
35988 }
35989
35990 if ( itemWorldTooltipSurface && isItemSameAsCurrent(item) )
35991 {
35992 return itemWorldTooltipSurface;
35993 }
35994
35995 type = item->type;
35996 status = item->status;
35997 beatitude = item->beatitude;
35998 count = item->count;
35999 appearance = item->appearance;
36000 identified = item->identified;
36001
36002 SDL_Rect tooltip;
36003 char buf[1024] = "";
36004 char buf2[1024] = "";
36005 int numHeaderLines = 1;
36006 int numDescLines = 3;
36007 Font* font = Font::get("fonts/pixel_maz.ttf#32#2");
36008 int headerSize = 16;
36009
36010 if ( !itemFrame )
36011 {
36012 itemFrame = new Frame("world tooltip frame");
36013 itemFrame->setSize(SDL_Rect{ 0, 0, player.hotbar.getSlotSize() - 4, player.hotbar.getSlotSize() - 4 });
36014 itemFrame->setUserData(&GAMEUI_FRAMEDATA_WORLDTOOLTIP_ITEM);
36015 createPlayerInventorySlotFrameElements(itemFrame);
36016 itemFrame->setSize(SDL_Rect{ 0, 0, player.hotbar.getSlotSize(), player.hotbar.getSlotSize() });
36017 }
36018
36019 updateSlotFrameFromItem(itemFrame, item);
36020
36021 // get tooltip name and background
36022 {
36023 if ( !item->identified )
36024 {
36025 if ( itemCategory(item) == BOOK )
36026 {
36027 snprintf(buf, sizeof(buf), "%s %s", ItemTooltips.getItemStatusAdjective(item->type, item->status).c_str(),
36028 Language::get(4214)); // brand new copy of
36029 snprintf(buf2, sizeof(buf), "%s (?)", getBookLocalizedNameFromIndex(item->appearance % numbooks).c_str());
36030 }
36031 else if ( itemCategory(item) == SCROLL )
36032 {
36033 snprintf(buf, sizeof(buf), "%s %s", ItemTooltips.getItemStatusAdjective(item->type, item->status).c_str(),
36034 items[item->type].getUnidentifiedName());
36035 snprintf(buf2, sizeof(buf), "%s %s (?)", Language::get(4215), item->getScrollLabel());
36036 }
36037 else
36038 {
36039 snprintf(buf, sizeof(buf), "%s %s (?)", ItemTooltips.getItemStatusAdjective(item->type, item->status).c_str(), item->getName());
36040 }
36041 }
36042 else
36043 {
36044 if ( (item->type == TOOL_SENTRYBOT || item->type == TOOL_DUMMYBOT || item->type == TOOL_SPELLBOT
36045 || item->type == TOOL_GYROBOT) )
36046 {
36047 int health = 100;
36048 if ( !item->tinkeringBotIsMaxHealth() )
36049 {
36050 health = 25 * (item->appearance % 10);
36051 if ( health == 0 && item->status != BROKEN )
36052 {
36053 health = 5;
36054 }
36055 }
36056 snprintf(buf, sizeof(buf), "%s %s (%d%%)", ItemTooltips.getItemStatusAdjective(item->type, item->status).c_str(), item->getName(), health);
36057 }
36058 else if ( item->type == ENCHANTED_FEATHER && item->identified )
36059 {
36060 snprintf(buf, sizeof(buf), "%s %s (%d%%) (%+d)", ItemTooltips.getItemStatusAdjective(item->type, item->status).c_str(),
36061 item->getName(), item->appearance % ENCHANTED_FEATHER_MAX_DURABILITY, item->beatitude);
36062 }
36063 else if ( itemCategory(item) == BOOK )
36064 {
36065 snprintf(buf, sizeof(buf), "%s %s", ItemTooltips.getItemStatusAdjective(item->type, item->status).c_str(),
36066 Language::get(4214)); // brand new copy of
36067 snprintf(buf2, sizeof(buf), "%s (%+d)", getBookLocalizedNameFromIndex(item->appearance % numbooks).c_str(), item->beatitude);
36068 }
36069 else
36070 {
36071 snprintf(buf, sizeof(buf), "%s %s (%+d)", ItemTooltips.getItemStatusAdjective(item->type, item->status).c_str(), item->getName(), item->beatitude);
36072 }
36073 }
36074
36075 if ( strcmp(buf2, "") )
36076 {
36077 numHeaderLines += 1;
36078 }
36079
36080 size_t longestLine = 200;
36081 if ( auto textGet = Text::get(buf, font->getName(), 0xFFFFFFFF, 0) )
36082 {
36083 if ( numHeaderLines == 1 && textGet->getWidth() > 300 )
36084 {
36085 Field* f = new Field(1024);
36086 f->setText(buf);
36087 f->setSize(SDL_Rect{ 0, 0, 300, 0 });
36088 f->reflowTextToFit(0);
36089 strcpy(buf, f->getText());
36090 delete f;
36091 f = nullptr;
36092 for ( size_t c = 0; c < strlen(buf); ++c )
36093 {
36094 if ( buf[c] == '\n' )
36095 {
36096 buf[c] = '\0';
36097 strcpy(buf2, buf + c + 1);
36098 std::string buf2Str = buf2;
36099 replace(buf2Str.begin(), buf2Str.end(), '\n', ' ');
36100 strcpy(buf2, buf2Str.c_str());
36101 break;
36102 }
36103 }
36104 textGet = Text::get(buf, font->getName(), 0xFFFFFFFF, 0);
36105 numHeaderLines++;
36106 }
36107 longestLine = std::max(longestLine, (size_t)textGet->getWidth());
36108 }
36109 if ( numHeaderLines > 1 )
36110 {
36111 if ( auto textGet = Text::get(buf2, font->getName(), 0xFFFFFFFF, 0) )
36112 {
36113 longestLine = std::max(longestLine, (size_t)textGet->getWidth());
36114 }
36115 }
36116 tooltip.w = 16 + 16 + 8 + longestLine;
36117
36118 if ( numHeaderLines > 1 )
36119 {
36120 headerSize = 42;
36121 }
36122 else
36123 {
36124 headerSize = 28;
36125 }
36126 itemFrame->setSize(SDL_Rect{
36127 tooltip.w - itemFrame->getSize().w - 16,
36128 headerSize + 4,
36129 itemFrame->getSize().w,
36130 itemFrame->getSize().h });
36131 tooltip.h = itemFrame->getSize().h + 8 + headerSize;
36132
36133 if ( itemWorldTooltipSurface )
36134 {
36135 SDL_FreeSurface(itemWorldTooltipSurface);
36136 itemWorldTooltipSurface = nullptr;
36137 }
36138 itemWorldTooltipSurface = SDL_CreateRGBSurface(0, tooltip.w, tooltip.h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
36139
36140 SDL_Rect imgPos{ 0, 0, 0, 0 };
36141 if ( numHeaderLines == 1 )
36142 {
36143 if ( auto img = Image::get("*#images/ui/Inventory/tooltips/Hover_TL00.png") )
36144 {
36145 imgPos.w = img->getWidth();
36146 imgPos.h = img->getHeight();
36147 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36148 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36149 }
36150 imgPos.x += imgPos.w;
36151 if ( auto img = Image::get("*#images/ui/Inventory/tooltips/Hover_T00.png") )
36152 {
36153 imgPos.w = tooltip.w - imgPos.w * 2;
36154 imgPos.h = img->getHeight();
36155 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36156 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36157 }
36158 imgPos.x += imgPos.w;
36159 if ( auto img = Image::get("*#images/ui/Inventory/tooltips/Hover_TR00.png") )
36160 {
36161 imgPos.w = img->getWidth();
36162 imgPos.h = img->getHeight();
36163 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36164 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36165 }
36166 }
36167 else
36168 {
36169 if ( auto img = Image::get("*#images/ui/Inventory/tooltips/Hover_TL00_2x.png") )
36170 {
36171 imgPos.w = img->getWidth();
36172 imgPos.h = img->getHeight();
36173 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36174 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36175 }
36176 imgPos.x += imgPos.w;
36177 if ( auto img = Image::get("*#images/ui/Inventory/tooltips/Hover_T00_2x.png") )
36178 {
36179 imgPos.w = tooltip.w - imgPos.w * 2;
36180 imgPos.h = img->getHeight();
36181 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36182 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36183 }
36184 imgPos.x += imgPos.w;
36185 if ( auto img = Image::get("*#images/ui/Inventory/tooltips/Hover_TR00_2x.png") )
36186 {
36187 imgPos.w = img->getWidth();
36188 imgPos.h = img->getHeight();
36189 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36190 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36191 }
36192 }
36193
36194 imgPos.x = 0;
36195 imgPos.y += imgPos.h;
36196 if ( auto img = Image::get("*#images/ui/Inventory/tooltips/Hover_L00.png") )
36197 {
36198 imgPos.w = img->getWidth();
36199 imgPos.h = tooltip.h - imgPos.h - 26;
36200 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36201 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36202 }
36203 imgPos.x += imgPos.w;
36204 if ( auto img = Image::get("*#images/ui/Inventory/tooltips/Hover_C00.png") )
36205 {
36206 imgPos.w = tooltip.w - imgPos.w * 2;
36207 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36208 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36209 }
36210 imgPos.x += imgPos.w;
36211 if ( auto img = Image::get("*#images/ui/Inventory/tooltips/Hover_R00.png") )
36212 {
36213 imgPos.w = img->getWidth();
36214 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36215 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36216 }
36217
36218 imgPos.x = 0;
36219 imgPos.y += imgPos.h;
36220 if ( auto img = Image::get("*#images/ui/Inventory/tooltips/Hover_BL01_NoPrompt.png") )
36221 {
36222 imgPos.w = img->getWidth();
36223 imgPos.h = img->getHeight();
36224 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36225 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36226 }
36227 imgPos.x += imgPos.w;
36228 if ( auto img = Image::get("*#images/ui/Inventory/tooltips/Hover_B00_NoPrompt.png") )
36229 {
36230 imgPos.w = tooltip.w - imgPos.w * 2;
36231 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36232 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36233 }
36234 imgPos.x += imgPos.w;
36235 if ( auto img = Image::get("*#images/ui/Inventory/tooltips/Hover_BR01_NoPrompt.png") )
36236 {
36237 imgPos.w = img->getWidth();
36238 imgPos.h = img->getHeight();
36239 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36240 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36241 }
36242 }
36243
36244 {
36245 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get("images/ui/HUD/hotbar/HUD_Quickbar_Slot_Box_02.png")->getSurf());
36246 SDL_Rect pos = SDL_Rect{ itemFrame->getSize().x, itemFrame->getSize().y,
36247 player.hotbar.getSlotSize(), player.hotbar.getSlotSize()};
36248 SDL_BlitSurfaceSDL_UpperBlit(srcSurf, nullptr, itemWorldTooltipSurface, &pos);
36249 }
36250
36251 for ( auto& f : itemFrame->getFrames() )
36252 {
36253 SDL_Rect pos = itemFrame->getSize();
36254 pos.x += f->getSize().x;
36255 pos.y += f->getSize().y;
36256
36257 if ( f->isDisabled() ) { continue; }
36258 for ( auto& img : f->getImages() )
36259 {
36260 if ( img->disabled ) { continue; }
36261 if ( img->path == "" ) { continue; }
36262 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(Image::get(img->path.c_str())->getSurf());
36263 Uint8 r, g, b, a;
36264 getColor(img->color, &r, &g, &b, &a);
36265 SDL_SetSurfaceAlphaMod(srcSurf, a);
36266 SDL_Rect imgPos = pos;
36267 imgPos.x += img->pos.x;
36268 imgPos.y += img->pos.y;
36269 imgPos.w = img->pos.w;
36270 imgPos.h = img->pos.h;
36271 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &imgPos);
36272 }
36273 for ( auto& field : f->getFields() )
36274 {
36275 if ( field->isDisabled() ) { continue; }
36276 SDL_Rect txtPos = pos;
36277 auto textGet = field->getTextObject();
36278 int x = field->getSize().x;
36279 int y = field->getSize().y;
36280 if ( field->getHJustify() == Field::justify_t::RIGHT
36281 || field->getHJustify() == Field::justify_t::BOTTOM )
36282 {
36283 x = field->getSize().x + field->getSize().w - textGet->getWidth();
36284 }
36285 if ( field->getVJustify() == Field::justify_t::BOTTOM
36286 || field->getVJustify() == Field::justify_t::RIGHT )
36287 {
36288 y = field->getSize().y + field->getSize().h - font->height();
36289 }
36290 txtPos.x += x;
36291 txtPos.y += y;
36292 txtPos.w = field->getSize().w;
36293 txtPos.h = field->getSize().h;
36294 SDL_BlitSurfaceSDL_UpperBlit(const_cast<SDL_Surface*>(textGet->getSurf()), nullptr, itemWorldTooltipSurface, &txtPos);
36295 }
36296 }
36297
36298 GLuint itemTexid = 0;
36299 SDL_Rect pos;
36300 if ( SDL_Surface* textSurf = const_cast<SDL_Surface*>(Text::get(buf, font->getName(),
36301 makeColor(67, 195, 157, 255), 0)->getSurf()) )
36302 {
36303 pos.x = 16 + 4;
36304 pos.y = 2;
36305 if ( numHeaderLines > 1 )
36306 {
36307 pos.y = -2;
36308 }
36309 SDL_BlitSurfaceSDL_UpperBlit(textSurf, nullptr, itemWorldTooltipSurface, &pos);
36310 }
36311 if ( numHeaderLines > 1 )
36312 {
36313 if ( SDL_Surface* textSurf = const_cast<SDL_Surface*>(Text::get(buf2, font->getName(),
36314 hudColors.characterSheetHeadingText, 0)->getSurf()) )
36315 {
36316 pos.x = 16 + 4;
36317 pos.y = (font->height() - 8);
36318 SDL_BlitSurfaceSDL_UpperBlit(textSurf, nullptr, itemWorldTooltipSurface, &pos);
36319 }
36320 }
36321
36322 {
36323 SDL_Rect identifyPos = pos;
36324 std::string identifyStr = ItemTooltips.adjectives["item_identified_status"]["unidentified"];
36325 if ( item->identified )
36326 {
36327 if ( item->beatitude > 0 )
36328 {
36329 identifyStr = ItemTooltips.adjectives["item_identified_status"]["blessed"];
36330 }
36331 else if ( item->beatitude < 0 )
36332 {
36333 identifyStr = ItemTooltips.adjectives["item_identified_status"]["cursed"];
36334 }
36335 else
36336 {
36337 identifyStr = ItemTooltips.adjectives["item_identified_status"]["uncursed"];
36338 }
36339 }
36340 identifyPos.y = headerSize;
36341 if ( auto textGet = Text::get(identifyStr.c_str(), font->getName(),
36342 hudColors.characterSheetNeutral, 0) )
36343 {
36344 if ( SDL_Surface* textSurf = const_cast<SDL_Surface*>(textGet->getSurf()) )
36345 {
36346 SDL_BlitSurfaceSDL_UpperBlit(textSurf, nullptr, itemWorldTooltipSurface, &identifyPos);
36347 }
36348 identifyPos.w = textGet->getWidth();
36349 identifyPos.h = textGet->getHeight();
36350 }
36351
36352 SDL_Rect wgtPos = pos;
36353 wgtPos.y = identifyPos.y + identifyPos.h + 2;
36354 wgtPos.x = identifyPos.x;
36355 const char* wgtImg = "images/ui/Inventory/tooltips/HUD_Tooltip_Icon_WGT_00.png";
36356 if ( auto imgGet = Image::get(wgtImg) )
36357 {
36358 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(imgGet->getSurf());
36359 wgtPos.w = imgGet->getWidth();
36360 wgtPos.h = imgGet->getHeight();
36361 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &wgtPos);
36362
36363 char wgtBuf[32];
36364 snprintf(wgtBuf, sizeof(wgtBuf), "%d", item->getWeight());
36365 if ( auto textGet = Text::get(wgtBuf, font->getName(),
36366 hudColors.characterSheetNeutral, 0) )
36367 {
36368 SDL_Surface* textSurf = const_cast<SDL_Surface*>(textGet->getSurf());
36369 wgtPos.x += wgtPos.w + 4;
36370 wgtPos.y = wgtPos.y + wgtPos.h / 2 - font->height() / 2;
36371 if ( wgtPos.y % 2 == 1 )
36372 {
36373 ++wgtPos.y;
36374 }
36375 SDL_BlitSurfaceSDL_UpperBlit(textSurf, nullptr, itemWorldTooltipSurface, &wgtPos);
36376 wgtPos.x += textGet->getWidth() + 8;
36377 }
36378 }
36379
36380 SDL_Rect goldPos = wgtPos;
36381 goldPos.y = identifyPos.y + identifyPos.h;
36382 const char* goldImg = "images/ui/Inventory/tooltips/HUD_Tooltip_Icon_Money_00.png";
36383 if ( auto imgGet = Image::get(goldImg) )
36384 {
36385 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(imgGet->getSurf());
36386 goldPos.w = imgGet->getWidth();
36387 goldPos.h = imgGet->getHeight();
36388 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, itemWorldTooltipSurface, &goldPos);
36389
36390 char goldBuf[32];
36391 if ( !item->identified && itemCategory(item) == GEM )
36392 {
36393 snprintf(goldBuf, sizeof(goldBuf), "%d", items[GEM_GLASS].value);
36394 }
36395 else
36396 {
36397 snprintf(goldBuf, sizeof(goldBuf), "%d", items[item->type].value);
36398 }
36399 if ( SDL_Surface* textSurf = const_cast<SDL_Surface*>(Text::get(goldBuf, font->getName(),
36400 hudColors.characterSheetNeutral, 0)->getSurf()) )
36401 {
36402 goldPos.x += goldPos.w + 4;
36403 goldPos.y = goldPos.y + goldPos.h / 2 - font->height() / 2;
36404 if ( goldPos.y % 2 == 1 )
36405 {
36406 ++goldPos.y;
36407 }
36408 SDL_BlitSurfaceSDL_UpperBlit(textSurf, nullptr, itemWorldTooltipSurface, &goldPos);
36409 }
36410 }
36411 }
36412 return itemWorldTooltipSurface;
36413}
36414
36415void Player::WorldUI_t::WorldTooltipDialogue_t::Dialogue_t::deactivate()
36416{
36417 parent = 0;
36418 x = 0.0;
36419 y = 0.0;
36420 z = 0.0;
36421 active = false;
36422 init = false;
36423 draw = false;
36424 alpha = 0.0;
36425 spawnTick = 0;
36426 updatedThisTick = 0;
36427 expiryTicks = 0;
36428 dialogueStringLength = 0;
36429 dialogueStrFull = "";
36430 dialogueStrCurrent = "";
36431 int langEntry = 0;
36432 DialogueType_t dialogueType = DIALOGUE_NONE;
36433 if ( dialogueTooltipSurface )
36434 {
36435 SDL_FreeSurface(dialogueTooltipSurface);
36436 dialogueTooltipSurface = nullptr;
36437 }
36438}
36439
36440void Player::WorldUI_t::WorldTooltipDialogue_t::update()
36441{
36442 playerDialogue.update();
36443 for ( auto& d : sharedDialogues )
36444 {
36445 d.second.update();
36446 }
36447 for ( auto it = sharedDialogues.cbegin(); it != sharedDialogues.cend(); )
36448 {
36449 if ( !it->second.active && it->second.alpha <= 0.0
36450 && (ticks - it->second.spawnTick >= it->second.expiryTicks) )
36451 {
36452 it = sharedDialogues.erase(it);
36453 }
36454 else
36455 {
36456 ++it;
36457 }
36458 }
36459}
36460
36461void Player::WorldUI_t::WorldTooltipDialogue_t::Dialogue_t::update()
36462{
36463 if ( !init )
36464 {
36465 return;
36466 }
36467 if ( player == -1 )
36468 {
36469 deactivate();
36470 return;
36471 }
36472 bool singleDisplayDialogue = (this == &players[player]->worldUI.worldTooltipDialogue.playerDialogue);
36473 if ( client_disconnected[player] )
36474 {
36475 active = false;
36476 }
36477
36478 Entity* parentEnt = uidToEntity(parent);
36479 bool expired = false;
36480 if ( !parentEnt )
36481 {
36482 active = false;
36483 expired = true;
36484 }
36485 else if ( ticks - spawnTick >= expiryTicks )
36486 {
36487 active = false;
36488 expired = true;
36489 }
36490
36491
36492 auto& setting = WorldDialogueSettings_t::settings[dialogueType];
36493 if ( parentEnt && setting.followEntity )
36494 {
36495 /*if ( parentEnt->bUseRenderInterpolation )
36496 {
36497 x = parentEnt->lerpRenderState.x.position * 16.0;
36498 y = parentEnt->lerpRenderState.y.position * 16.0;
36499 z = parentEnt->lerpRenderState.z.position + setting.offsetZ;
36500 }
36501 else*/
36502 {
36503 x = parentEnt->x;
36504 y = parentEnt->y;
36505 z = parentEnt->z + setting.offsetZ;
36506 }
36507 }
36508
36509 real_t dx, dy;
36510 auto& camera = cameras[player];
36511 dx = x - camera.x * 16.0;
36512 dy = y - camera.y * 16.0;
36513 if ( dx * dx + dy * dy > setting.fadeDist * setting.fadeDist )
36514 {
36515 active = false;
36516 }
36517 else if ( !singleDisplayDialogue && !expired )
36518 {
36519 active = true;
36520 }
36521
36522 if ( ticks != updatedThisTick )
36523 {
36524 if ( active )
36525 {
36526 alpha = std::min(1.0, alpha + .15);
36527 animZ -= animZ * 0.25;
36528 animZ = std::max(0.1, animZ);
36529 }
36530 else
36531 {
36532 alpha = std::max(0.0, alpha - .12);
36533 animZ += 0.05;
36534 animZ = std::min(1.5, animZ);
36535 if ( alpha <= 0.0 )
36536 {
36537 if ( expired || singleDisplayDialogue )
36538 {
36539 deactivate();
36540 }
36541 return;
36542 }
36543 }
36544 }
36545
36546 if ( setting.textDelay <= 0 )
36547 {
36548 dialogueStrCurrent = dialogueStrFull;
36549 dialogueStringLength = dialogueStrFull.size();
36550 }
36551 else if ( ticks - updatedThisTick > (Uint32)(setting.textDelay - 1) )
36552 {
36553 size_t fullLen = dialogueStrFull.size();
36554 if ( dialogueStringLength < fullLen )
36555 {
36556 if ( dialogueStringLength + 1 == fullLen )
36557 {
36558 dialogueStrCurrent = dialogueStrFull;
36559 }
36560 else
36561 {
36562 dialogueStrCurrent = dialogueStrFull.substr(0U, dialogueStringLength + 1);
36563 ++dialogueStringLength;
36564 }
36565 }
36566 else if ( dialogueStringLength > fullLen )
36567 {
36568 dialogueStringLength = fullLen;
36569 }
36570 }
36571 draw = true;
36572
36573 if ( ticks != updatedThisTick )
36574 {
36575 blitDialogueTooltip();
36576 }
36577 updatedThisTick = ticks;
36578}
36579
36580void Player::WorldUI_t::WorldTooltipDialogue_t::createDialogueTooltip(Uint32 uid,
36581 Player::WorldUI_t::WorldTooltipDialogue_t::DialogueType_t type, char const * const message, ...)
36582{
36583 if ( multiplayer == SERVER1 )
36584 {
36585 if ( player.playernum != clientnum )
36586 {
36587 if ( client_disconnected[player.playernum] )
36588 {
36589 return;
36590 }
36591 char buf[1024] = { 0 };
36592
36593 va_list argptr;
36594 va_start(argptr, message)__builtin_va_start(argptr, message);
36595 vsnprintf(buf, sizeof(buf), message, argptr);
36596 va_end(argptr)__builtin_va_end(argptr);
36597
36598 strcpy((char*)net_packet->data, "BUBL");
36599 SDLNet_Write32(uid, &net_packet->data[4])_SDLNet_Write32(uid, &net_packet->data[4]);
36600 net_packet->data[8] = Uint8(type);
36601 strcpy((char*)(&net_packet->data[9]), buf);
36602 net_packet->address.host = net_clients[player.playernum - 1].host;
36603 net_packet->address.port = net_clients[player.playernum - 1].port;
36604 net_packet->len = 9 + strlen(buf) + 1;
36605 sendPacketSafe(net_sock, -1, net_packet, player.playernum - 1);
36606 return;
36607 }
36608 }
36609
36610 Dialogue_t* d = &playerDialogue;
36611 if ( !(type == DIALOGUE_GRAVE || type == DIALOGUE_SIGNPOST
36612 || type == DIALOGUE_NPC) )
36613 {
36614 if ( type == DIALOGUE_ATTACK && (sharedDialogues.find(uid) != sharedDialogues.end()) )
36615 {
36616 if ( sharedDialogues[uid].dialogueType == DIALOGUE_ATTACK )
36617 {
36618 return; // don't process combat taunts unless previous finished
36619 }
36620 }
36621 d = &sharedDialogues[uid];
36622 }
36623 d->player = player.playernum;
36624 Uint32 oldUid = d->parent;
36625 real_t oldAlpha = d->alpha;
36626 real_t oldAnimZ = d->animZ;
36627 d->deactivate();
36628 Entity* parentEnt = uidToEntity(uid);
36629 if ( !parentEnt )
36630 {
36631 return;
36632 }
36633 d->parent = uid;
36634
36635 char buf[1024] = { 0 };
36636
36637 va_list argptr;
36638 va_start(argptr, message)__builtin_va_start(argptr, message);
36639 vsnprintf(buf, sizeof(buf), message, argptr);
36640 va_end(argptr)__builtin_va_end(argptr);
36641
36642 if (player.playernum == clientnum)
36643 {
36644 messagePlayer(player.playernum, MESSAGE_CHATTER, buf);
36645 }
36646
36647 d->dialogueStrFull = buf;
36648 d->spawnTick = ticks;
36649 d->updatedThisTick = 0;
36650 d->dialogueType = type;
36651
36652 auto& setting = WorldDialogueSettings_t::settings[d->dialogueType];
36653
36654 /*if ( parentEnt->bUseRenderInterpolation )
36655 {
36656 d->x = parentEnt->lerpRenderState.x.position * 16.0;
36657 d->y = parentEnt->lerpRenderState.y.position * 16.0;
36658 d->z = parentEnt->lerpRenderState.z.position + setting.offsetZ;
36659 }
36660 else*/
36661 {
36662 d->x = parentEnt->x;
36663 d->y = parentEnt->y;
36664 d->z = parentEnt->z + setting.offsetZ;
36665 }
36666 d->animZ = 1.5;
36667 d->drawScale = 0.1 + setting.scaleMod;
36668
36669 d->active = true;
36670 d->init = true;
36671 d->alpha = 0.0;
36672
36673 if ( d->parent == oldUid )
36674 {
36675 d->alpha = oldAlpha;
36676 d->animZ = oldAnimZ;
36677 }
36678
36679 if ( !d->dialogueField )
36680 {
36681 d->dialogueField = new Field(1024);
36682 d->dialogueField->setFont("fonts/pixel_maz_multiline.ttf#16");
36683 }
36684 d->dialogueField->setText(d->dialogueStrFull.c_str());
36685 int maxWidth = setting.maxWidth;
36686 d->dialogueField->setSize(SDL_Rect{ 0, 0, maxWidth, 0 });
36687 d->dialogueField->reflowTextToFit(0);
36688 int numLines = d->dialogueField->getNumTextLines();
36689 if ( Font* actualFont = Font::get(d->dialogueField->getFont()) )
36690 {
36691 auto textHeight = numLines * actualFont->height(true) + 16;
36692 if ( auto textGet = Text::get(d->dialogueField->getLongestLine().c_str(),
36693 d->dialogueField->getFont(), d->dialogueField->getTextColor(),
36694 d->dialogueField->getOutlineColor()) )
36695 {
36696 d->dialogueField->setSize(SDL_Rect{ 0, 0, (int)textGet->getWidth() + 16, textHeight });
36697 }
36698 else
36699 {
36700 d->dialogueField->setSize(SDL_Rect{ 0, 0, maxWidth, textHeight });
36701 }
36702 }
36703 d->expiryTicks = setting.baseTicksToDisplay;
36704 if ( numLines > 1 )
36705 {
36706 d->expiryTicks += (numLines - 1) * setting.extraTicksPerLine;
36707 }
36708 d->dialogueStrFull = d->dialogueField->getText();
36709
36710 if ( d->dialogueType == DIALOGUE_NPC )
36711 {
36712 // insta-show the first line (e.g The NPC:)
36713 size_t found = d->dialogueStrFull.find('\n');
36714 if ( found != std::string::npos )
36715 {
36716 size_t foundColon = d->dialogueStrFull.find(':');
36717 if ( foundColon != std::string::npos
36718 && foundColon < found )
36719 {
36720 d->dialogueStringLength = found;
36721
36722 while ( found != std::string::npos )
36723 {
36724 if ( found + 1 < d->dialogueStrFull.length() )
36725 {
36726 if ( d->dialogueStrFull[found + 1] != ' ' )
36727 {
36728 d->dialogueStrFull.insert(found + 1, 1, ' ');
36729 ++found;
36730 }
36731 }
36732 found = d->dialogueStrFull.find('\n', found + 1);
36733 }
36734 }
36735 }
36736 d->dialogueField->setText(d->dialogueStrFull.c_str());
36737 }
36738}
36739
36740SDL_Surface* Player::WorldUI_t::WorldTooltipDialogue_t::Dialogue_t::blitDialogueTooltip()
36741{
36742 auto& setting = WorldDialogueSettings_t::settings[dialogueType];
36743 const int pointerExtraHeight = 16;
36744 SDL_Rect tooltip{ 0, 0, dialogueField->getSize().w, dialogueField->getSize().h };
36745 tooltip.h += 16 + setting.padx * 2 + pointerExtraHeight;
36746 tooltip.w += 16 + setting.pady * 2;
36747 const int numLines = dialogueField->getNumTextLines();
36748 if ( numLines > 1 )
36749 {
36750 tooltip.h += setting.padAfterFirstLine;
36751 }
36752
36753 if ( dialogueTooltipSurface )
36754 {
36755 SDL_FreeSurface(dialogueTooltipSurface);
36756 dialogueTooltipSurface = nullptr;
36757 }
36758
36759 dialogueTooltipSurface = SDL_CreateRGBSurface(0, tooltip.w, tooltip.h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
36760
36761 SDL_Rect imgPos{ 0, 0, 0, 0 };
36762 if ( dialogueType == DIALOGUE_GRAVE )
36763 {
36764 dialogueField->setTextColor(makeColor(51, 45, 59, 255));
36765 dialogueField->setOutlineColor(makeColor(115, 127, 134, 255));
36766
36767 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Grave_TL.png") )
36768 {
36769 imgPos.w = img->getWidth();
36770 imgPos.h = img->getHeight();
36771 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36772 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36773 }
36774 imgPos.x += imgPos.w;
36775 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Grave_T.png") )
36776 {
36777 imgPos.w = tooltip.w - imgPos.w * 2;
36778 imgPos.h = img->getHeight();
36779 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36780 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36781 }
36782 imgPos.x += imgPos.w;
36783 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Grave_TR.png") )
36784 {
36785 imgPos.w = img->getWidth();
36786 imgPos.h = img->getHeight();
36787 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36788 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36789 }
36790
36791 imgPos.x = 0;
36792 imgPos.y += imgPos.h;
36793 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Grave_L.png") )
36794 {
36795 imgPos.w = img->getWidth();
36796 imgPos.h = tooltip.h - imgPos.h - 26 - pointerExtraHeight;
36797 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36798 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36799 }
36800 imgPos.x += imgPos.w;
36801 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Grave_Color.png") )
36802 {
36803 imgPos.w = tooltip.w - imgPos.w * 2;
36804 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36805 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36806 }
36807 imgPos.x += imgPos.w;
36808 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Grave_R.png") )
36809 {
36810 imgPos.w = img->getWidth();
36811 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36812 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36813 }
36814
36815 imgPos.x = 0;
36816 imgPos.y += imgPos.h;
36817 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Grave_BL.png") )
36818 {
36819 imgPos.w = img->getWidth();
36820 imgPos.h = img->getHeight();
36821 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36822 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36823 }
36824 imgPos.x += imgPos.w;
36825 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Grave_B.png") )
36826 {
36827 imgPos.w = tooltip.w - imgPos.w * 2;
36828 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36829 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36830 }
36831 imgPos.x += imgPos.w;
36832 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Grave_BR.png") )
36833 {
36834 imgPos.w = img->getWidth();
36835 imgPos.h = img->getHeight();
36836 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36837 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36838 }
36839
36840 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Grave_Pointer.png") )
36841 {
36842 imgPos.x = tooltip.w / 2;
36843 if ( imgPos.x % 2 == 1 )
36844 {
36845 ++imgPos.x;
36846 }
36847 imgPos.y += imgPos.h - 6;
36848 imgPos.w = img->getWidth();
36849 imgPos.h = img->getHeight();
36850 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36851 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36852 }
36853 }
36854 else if ( dialogueType == DIALOGUE_SIGNPOST )
36855 {
36856 dialogueField->setTextColor(makeColor(29, 16, 11, 255));
36857 dialogueField->setOutlineColor(makeColor(82, 66, 60, 255));
36858
36859 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Sign_TL.png") )
36860 {
36861 imgPos.w = img->getWidth();
36862 imgPos.h = img->getHeight();
36863 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36864 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36865 }
36866 imgPos.x += imgPos.w;
36867 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Sign_T.png") )
36868 {
36869 imgPos.w = tooltip.w - imgPos.w * 2;
36870 imgPos.h = img->getHeight();
36871 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36872 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36873 }
36874 imgPos.x += imgPos.w;
36875 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Sign_TR.png") )
36876 {
36877 imgPos.w = img->getWidth();
36878 imgPos.h = img->getHeight();
36879 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36880 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36881 }
36882
36883 imgPos.x = 0;
36884 imgPos.y += imgPos.h;
36885 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Sign_L.png") )
36886 {
36887 imgPos.w = img->getWidth();
36888 imgPos.h = tooltip.h - imgPos.h - 26 - pointerExtraHeight;
36889 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36890 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36891 }
36892 imgPos.x += imgPos.w;
36893 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Sign_Color.png") )
36894 {
36895 imgPos.w = tooltip.w - imgPos.w * 2;
36896 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36897 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36898 }
36899 imgPos.x += imgPos.w;
36900 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Sign_R.png") )
36901 {
36902 imgPos.w = img->getWidth();
36903 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36904 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36905 }
36906
36907 imgPos.x = 0;
36908 imgPos.y += imgPos.h;
36909 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Sign_BL.png") )
36910 {
36911 imgPos.w = img->getWidth();
36912 imgPos.h = img->getHeight();
36913 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36914 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36915 }
36916 imgPos.x += imgPos.w;
36917 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Sign_B.png") )
36918 {
36919 imgPos.w = tooltip.w - imgPos.w * 2;
36920 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36921 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36922 }
36923 imgPos.x += imgPos.w;
36924 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Sign_BR.png") )
36925 {
36926 imgPos.w = img->getWidth();
36927 imgPos.h = img->getHeight();
36928 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36929 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36930 }
36931
36932 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_Sign_Pointer.png") )
36933 {
36934 imgPos.x = tooltip.w / 2;
36935 if ( imgPos.x % 2 == 1 )
36936 {
36937 ++imgPos.x;
36938 }
36939 imgPos.y += imgPos.h - 6;
36940 imgPos.w = img->getWidth();
36941 imgPos.h = img->getHeight();
36942 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36943 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36944 }
36945 }
36946 else if ( dialogueType == DIALOGUE_NPC
36947 || dialogueType == DIALOGUE_FOLLOWER_CMD
36948 || dialogueType == DIALOGUE_BROADCAST
36949 || dialogueType == DIALOGUE_ATTACK )
36950 {
36951 dialogueField->setTextColor(makeColor(29, 16, 11, 255));
36952 dialogueField->setOutlineColor(makeColor(186, 169, 128, 255));
36953
36954 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_NPC_TL.png") )
36955 {
36956 imgPos.w = img->getWidth();
36957 imgPos.h = img->getHeight();
36958 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36959 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36960 }
36961 imgPos.x += imgPos.w;
36962 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_NPC_T.png") )
36963 {
36964 imgPos.w = tooltip.w - imgPos.w * 2;
36965 imgPos.h = img->getHeight();
36966 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36967 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36968 }
36969 imgPos.x += imgPos.w;
36970 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_NPC_TR.png") )
36971 {
36972 imgPos.w = img->getWidth();
36973 imgPos.h = img->getHeight();
36974 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36975 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36976 }
36977
36978 imgPos.x = 0;
36979 imgPos.y += imgPos.h;
36980 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_NPC_L.png") )
36981 {
36982 imgPos.w = img->getWidth();
36983 imgPos.h = tooltip.h - imgPos.h - 26 - pointerExtraHeight;
36984 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36985 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36986 }
36987 imgPos.x += imgPos.w;
36988 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_NPC_Color.png") )
36989 {
36990 imgPos.w = tooltip.w - imgPos.w * 2;
36991 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36992 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
36993 }
36994 imgPos.x += imgPos.w;
36995 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_NPC_R.png") )
36996 {
36997 imgPos.w = img->getWidth();
36998 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
36999 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
37000 }
37001
37002 imgPos.x = 0;
37003 imgPos.y += imgPos.h;
37004 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_NPC_BL.png") )
37005 {
37006 imgPos.w = img->getWidth();
37007 imgPos.h = img->getHeight();
37008 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
37009 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
37010 }
37011 imgPos.x += imgPos.w;
37012 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_NPC_B.png") )
37013 {
37014 imgPos.w = tooltip.w - imgPos.w * 2;
37015 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
37016 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
37017 }
37018 imgPos.x += imgPos.w;
37019 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_NPC_BR.png") )
37020 {
37021 imgPos.w = img->getWidth();
37022 imgPos.h = img->getHeight();
37023 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
37024 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
37025 }
37026
37027 if ( auto img = Image::get("*#images/ui/TextBubbles/Textbox_NPC_Pointer.png") )
37028 {
37029 imgPos.x = tooltip.w / 2;
37030 if ( imgPos.x % 2 == 1 )
37031 {
37032 ++imgPos.x;
37033 }
37034 imgPos.y += imgPos.h - 6;
37035 imgPos.w = img->getWidth();
37036 imgPos.h = img->getHeight();
37037 SDL_Surface* srcSurf = const_cast<SDL_Surface*>(img->getSurf());
37038 SDL_BlitScaledSDL_UpperBlitScaled(srcSurf, nullptr, dialogueTooltipSurface, &imgPos);
37039 }
37040 }
37041
37042 SDL_Rect txtPos{ 16 + setting.padx, 9 + setting.pady, 0, 0 };
37043 int fontHeight = Font::get(dialogueField->getFont())->height(true);
37044 std::string buf;
37045 int linesDone = 0;
37046 for ( size_t i = 0; i < dialogueStrCurrent.length(); ++i )
37047 {
37048 if ( dialogueStrCurrent[i] == '\n' || i == dialogueStrCurrent.length() - 1 )
37049 {
37050 if ( i == dialogueStrCurrent.length() - 1
37051 && dialogueStrCurrent[i] != '\r'
37052 && dialogueStrCurrent[i] != '\n' )
37053 {
37054 buf += dialogueStrCurrent[i];
37055 }
37056 if ( auto textGet = Text::get(buf.c_str(), dialogueField->getFont(),
37057 dialogueField->getTextColor(), dialogueField->getOutlineColor()) )
37058 {
37059 bool quoteOffsetY = 0;
37060 size_t foundEOL = dialogueStrCurrent.find('\0', i + 1);
37061 size_t foundNewLine = dialogueStrCurrent.find('\n', i + 1);
37062 size_t foundQuote = dialogueStrCurrent.find('\"', i + 1);
37063 if ( foundNewLine != std::string::npos && foundEOL != std::string::npos
37064 && foundNewLine < foundEOL )
37065 {
37066 if ( foundQuote < foundNewLine )
37067 {
37068 // this line has a quote after first char
37069 // adjust line spacing to account for offset quote introduces
37070 quoteOffsetY = -2;
37071 }
37072 }
37073 else if ( foundEOL != std::string::npos )
37074 {
37075 if ( foundQuote < foundEOL )
37076 {
37077 // this line has a quote after first char
37078 // adjust line spacing to account for offset quote introduces
37079 quoteOffsetY = -2;
37080 }
37081 }
37082 txtPos.y += quoteOffsetY;
37083 SDL_BlitSurfaceSDL_UpperBlit(const_cast<SDL_Surface*>(textGet->getSurf()), nullptr, dialogueTooltipSurface, &txtPos);
37084 txtPos.y -= quoteOffsetY;
37085 txtPos.y += fontHeight;
37086 if ( linesDone == 0 )
37087 {
37088 txtPos.y += setting.padAfterFirstLine;
37089 }
37090 }
37091 buf = "";
37092 ++linesDone;
37093 continue;
37094 }
37095 else if ( dialogueStrCurrent[i] != '\r' )
37096 {
37097 buf += dialogueStrCurrent[i];
37098 }
37099 }
37100 return dialogueTooltipSurface;
37101}
37102
37103void handleDamageIndicatorTicks()
37104{
37105 for ( int i = 0; i < MAXPLAYERS4; ++i )
37106 {
37107 for ( auto& ind : DamageIndicatorHandler.indicators[i] )
37108 {
37109 if ( ind.ticks > 0 )
37110 {
37111 --ind.ticks;
37112 }
37113 }
37114 }
37115}
37116
37117void DamageIndicatorHandler_t::update()
37118{
37119 for ( int i = 0; i < MAXPLAYERS4; ++i )
37120 {
37121 for ( auto it = indicators[i].begin(); it != indicators[i].end(); )
37122 {
37123 it->process();
37124 if ( it->expired )
37125 {
37126 it = indicators[i].erase(it);
37127 }
37128 else
37129 {
37130 ++it;
37131 }
37132 }
37133 }
37134}
37135void DamageIndicatorHandler_t::insert(const int player, const real_t _x, const real_t _y, const bool damaged)
37136{
37137 for ( auto it = indicators[player].begin(); it != indicators[player].end(); ++it )
37138 {
37139 if ( !it->expired && it->x == _x && it->y == _y && it->hitDealtDamage == damaged )
37140 {
37141 // matching, delete this one
37142 indicators[player].erase(it);
37143 break;
37144 }
37145 }
37146 indicators[player].push_back(DamageIndicator_t(player));
37147 auto& i = indicators[player].at(indicators[player].size() - 1);
37148 i.alpha = 255.0;
37149 i.ticks = damageIndicatorSettings.deleteAfterTicks;
37150 i.x = _x;
37151 i.y = _y;
37152 i.flashTicks = ::ticks;
37153 i.flashAnimState = -1;
37154 i.hitDealtDamage = damaged;
37155}
37156
37157static ConsoleVariable<int> cvar_indicatoranimdebug("/indicatoranimdebug", 1);
37158
37159void DamageIndicatorHandler_t::DamageIndicator_t::process()
37160{
37161 if ( expired )
37162 {
37163 return;
37164 }
37165 double tangent = atan2(y / 16 - cameras[player].y, x / 16 - cameras[player].x);
37166 double angle = tangent - cameras[player].ang;
37167 angle += 3 * PI3.14159265358979323846 / 2;
37168 while ( angle >= PI3.14159265358979323846 )
37169 {
37170 angle -= PI3.14159265358979323846 * 2;
37171 }
37172 while ( angle < -PI3.14159265358979323846 )
37173 {
37174 angle += PI3.14159265358979323846 * 2;
37175 }
37176
37177 const int framesPerAnimation = 1 * *cvar_indicatoranimdebug;
37178 const int numAnimationFrames = damageIndicatorSettings.deleteAfterTicks * *cvar_indicatoranimdebug;
37179 auto layout = DamageIndicatorSettings_t::LAYOUT_DEFAULT;
37180 if ( players[player]->bUseCompactGUIHeight() && players[player]->bUseCompactGUIWidth() )
37181 {
37182 layout = DamageIndicatorSettings_t::LAYOUT_4P;
37183 }
37184 else if ( !players[player]->bUseCompactGUIHeight() && players[player]->bUseCompactGUIWidth() )
37185 {
37186 layout = DamageIndicatorSettings_t::LAYOUT_2P_TALL;
37187 }
37188 else if ( players[player]->bUseCompactGUIHeight() && !players[player]->bUseCompactGUIWidth() )
37189 {
37190 layout = DamageIndicatorSettings_t::LAYOUT_2P_WIDE;
37191 }
37192 size = damageIndicatorSettings.settings[layout].image_size;
37193 auto& indicatorDamagePaths = damageIndicatorSettings.indicatorDamageFramePaths;
37194 auto& indicatorBlockPaths = damageIndicatorSettings.indicatorBlockedFramePaths;
37195 std::string imagePath = hitDealtDamage ? indicatorDamagePaths[0] : indicatorBlockPaths[0];
37196 if ( flashTicks > 0 )
37197 {
37198 if ( flashAnimState > numAnimationFrames )
37199 {
37200 flashTicks = 0;
37201 flashAnimState = -1;
37202 alpha = 0.0;
37203 }
37204 else
37205 {
37206 if ( ::ticks == flashTicks )
37207 {
37208 flashAnimState = 1;
37209 flashProcessedOnTick = ::ticks;
37210 }
37211 else if ( (flashProcessedOnTick != ::ticks)
37212 && (::ticks > flashTicks)
37213 && (::ticks - flashTicks) % framesPerAnimation == 0 )
37214 {
37215 ++flashAnimState;
37216 flashProcessedOnTick = ::ticks;
37217 }
37218
37219 if ( flashAnimState <= 6 )
37220 {
37221 alpha = 255.0;
37222 }
37223
37224 if ( flashAnimState == 0 )
37225 {
37226 imagePath = hitDealtDamage ? indicatorDamagePaths[0] : indicatorBlockPaths[0];
37227 }
37228 else if ( flashAnimState >= 1 && flashAnimState <= 2 )
37229 {
37230 imagePath = hitDealtDamage ? indicatorDamagePaths[0] : indicatorBlockPaths[0];
37231 }
37232 else if ( flashAnimState == 3 )
37233 {
37234 imagePath = hitDealtDamage ? indicatorDamagePaths[2] : indicatorBlockPaths[2];
37235 }
37236 else if ( flashAnimState >= 4 && flashAnimState <= 5 )
37237 {
37238 imagePath = hitDealtDamage ? indicatorDamagePaths[1] : indicatorBlockPaths[1];
37239 }
37240 else if ( flashAnimState == 6 )
37241 {
37242 imagePath = hitDealtDamage ? indicatorDamagePaths[3] : indicatorBlockPaths[3];
37243 }
37244 else if ( flashAnimState >= damageIndicatorSettings.fadeAfterTicks )
37245 {
37246 imagePath = hitDealtDamage ? indicatorDamagePaths[0] : indicatorBlockPaths[0];
37247 int decrement = 20;
37248 real_t fpsScale = (getFPSScale(60.0)) / damageIndicatorSettings.fadeSpeed;
37249 decrement *= fpsScale;
37250 alpha = std::max(0, (int)alpha - decrement);
37251 }
37252 }
37253 }
37254
37255 switch ( size )
37256 {
37257 case 3:
37258 imagePath += "_3x.png";
37259 break;
37260 case 2:
37261 imagePath += "_2x.png";
37262 break;
37263 case 1:
37264 default:
37265 imagePath += "_1x.png";
37266 break;
37267 }
37268
37269 if ( auto imgGet = Image::get(imagePath.c_str()) )
37270 {
37271 SDL_Rect pos;
37272 pos.x = players[player]->camera_midx();
37273 pos.y = players[player]->camera_midy();
37274 const float factorX = (float)xres / Frame::virtualScreenX;
37275 const float factorY = (float)yres / Frame::virtualScreenY;
37276 pos.x += damageIndicatorSettings.settings[layout].radius_x * cos(angle) * factorX;
37277 pos.y += damageIndicatorSettings.settings[layout].radius_y * sin(angle) * factorY;
37278 pos.w = imgGet->getWidth() * factorX;
37279 pos.h = imgGet->getHeight() * factorY;
37280 if ( stats[player]->HP > 0 )
37281 {
37282 const SDL_Rect viewport{ 0, 0, xres, yres };
37283 imgGet->drawRotated(nullptr, pos, viewport, makeColor(255, 255, 255, (Uint8)alpha), angle);
37284 }
37285 }
37286
37287 if ( alpha <= 0 || ticks == 0 )
37288 {
37289 expired = true;
37290 }
37291}
37292
37293void createLevelUpFrame(const int player)
37294{
37295 auto& hud_t = players[player]->hud;
37296 hud_t.levelupFrame = gameUIFrame[player]->addFrame("levelup");
37297 hud_t.levelupFrame->setHollow(true);
37298 hud_t.levelupFrame->setInheritParentFrameOpacity(false);
37299 hud_t.levelupFrame->setOpacity(100.0);
37300 hud_t.levelupFrame->setDisabled(true);
37301 hud_t.levelupFrame->setOwner(player);
37302
37303 auto lvlupImg = hud_t.levelupFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "images/ui/HUD/lvluptext.png", "lvl up img");
37304 lvlupImg->disabled = true;
37305
37306 auto statsFrame = hud_t.levelupFrame->addFrame("stats");
37307 statsFrame->setDisabled(true);
37308 statsFrame->setHollow(true);
37309 char name[32];
37310 std::string font = "fonts/pixelmix.ttf#16#2";
37311 for ( int i = 0; i < 6; ++i )
37312 {
37313 snprintf(name, sizeof(name), "stat %d", i);
37314 auto statFrame = statsFrame->addFrame(name);
37315 statFrame->setDisabled(true);
37316 statFrame->setHollow(true);
37317 statFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "stat img");
37318
37319 auto statCurrentTxt = statFrame->addField("stat current", 32);
37320 statCurrentTxt->setFont(font.c_str());
37321 statCurrentTxt->setHJustify(Field::justify_t::RIGHT);
37322 statCurrentTxt->setVJustify(Field::justify_t::TOP);
37323 statCurrentTxt->setText("0");
37324
37325 auto statIncreaseTxt = statFrame->addField("stat increase", 32);
37326 statIncreaseTxt->setFont(font.c_str());
37327 statIncreaseTxt->setHJustify(Field::justify_t::LEFT);
37328 statIncreaseTxt->setVJustify(Field::justify_t::TOP);
37329 statIncreaseTxt->setText("0");
37330 }
37331}
37332
37333static ConsoleVariable<int> cvar_lvlup_sfx("/lvlup_sfx", 526);
37334Uint32 levelupSoundDelay = 0;
37335void LevelUpAnimation_t::addLevelUp(const int currentLvl, const int increaseLvl, std::vector<LevelUp_t::StatUp_t>& statInfo)
37336{
37337 if ( lvlUps.size() > 2 )
37338 {
37339 // stack everything onto the back
37340 auto& lvlUp = lvlUps.back();
37341 for ( auto& info : statInfo )
37342 {
37343 for ( auto& currentInfo : lvlUp.statUps )
37344 {
37345 if ( currentInfo.whichStat == info.whichStat )
37346 {
37347 if ( info.increaseStat > 0 ) // ignore negatives
37348 {
37349 currentInfo.increaseStat += info.increaseStat;
37350 }
37351 }
37352 }
37353 }
37354 }
37355 else
37356 {
37357 bool inProgress = !lvlUps.empty();
37358 lvlUps.push_back(LevelUp_t(currentLvl, increaseLvl));
37359 auto& lvlUp = lvlUps.back();
37360 if ( inProgress )
37361 {
37362 // skip some animation
37363 lvlUp.titleFinishAnim = true;
37364 lvlUp.animTitleFade = 1.0;
37365 lvlUp.ticksToLive = 3 * TICKS_PER_SECOND50;
37366 }
37367 else
37368 {
37369 for ( int i = 0; i < MAXPLAYERS4; ++i )
37370 {
37371 if ( &levelUpAnimation[i] == this )
37372 {
37373 if ( levelupSoundDelay == 0 || ((ticks - levelupSoundDelay) > (TICKS_PER_SECOND50 / 10)) )
37374 {
37375 playSoundNotificationPlayer(i, *cvar_lvlup_sfx, 255);
37376 levelupSoundDelay = TICKS_PER_SECOND50 / 10;
37377 }
37378 break;
37379 }
37380 }
37381 levelupSoundDelay = ticks;
37382 }
37383
37384 for ( int i = 0; i < NUMSTATS; ++i )
37385 {
37386 for ( auto& statUp : statInfo )
37387 {
37388 if ( statUp.whichStat == i )
37389 {
37390 lvlUp.statUps.push_back(statUp);
37391 }
37392 }
37393 }
37394 }
37395}
37396
37397void LevelUpAnimation_t::LevelUp_t::StatUp_t::setAnimatePosition(int destx, int desty, int destw, int desth)
37398{
37399 animateStartX = pos.x;
37400 animateStartY = pos.y;
37401 animateStartW = pos.w;
37402 animateStartH = pos.h;
37403 animateSetpointX = destx;
37404 animateSetpointY = desty;
37405 animateSetpointW = destw;
37406 animateSetpointH = desth;
37407 animateX = 0.0;
37408 animateY = 0.0;
37409 animateW = 0.0;
37410 animateH = 0.0;
37411}
37412
37413void LevelUpAnimation_t::LevelUp_t::StatUp_t::setAnimatePosition(int destx, int desty)
37414{
37415 animateStartX = pos.x;
37416 animateStartY = pos.y;
37417 animateStartW = pos.w;
37418 animateStartH = pos.h;
37419 animateSetpointX = destx;
37420 animateSetpointY = desty;
37421 animateSetpointW = 0;
37422 animateSetpointH = 0;
37423 animateX = 0.0;
37424 animateY = 0.0;
37425 animateW = 0.0;
37426 animateH = 0.0;
37427}
37428
37429static ConsoleVariable<int> cvar_skill_ding_sfx("/skill_sfx_ding", 554);
37430static ConsoleVariable<int> cvar_lvl_ding_sfx("/lvl_sfx_ding", 555);
37431static ConsoleVariable<int> cvar_skill_sfx_volume("/skill_sfx_volume", 128);
37432static ConsoleVariable<int> cvar_skill_newspell_sfx("/skill_sfx_newspell", 560);
37433
37434bool SkillUpAnimation_t::soundIndexUsedForNotification(const int index)
37435{
37436 if ( index == 0 ) { return false; }
37437 if ( index >= *cvar_skill_ding_sfx && index <= *cvar_skill_ding_sfx + 3 )
37438 {
37439 return true;
37440 }
37441 else if ( index == CalloutRadialMenu::CALLOUT_SFX_NEGATIVE
37442 || index == CalloutRadialMenu::CALLOUT_SFX_NEUTRAL
37443 || index == CalloutRadialMenu::CALLOUT_SFX_POSITIVE )
37444 {
37445 return true;
37446 }
37447 else if ( index == Message::CHAT_MESSAGE_SFX )
37448 {
37449 return true;
37450 }
37451 else if ( index == *cvar_lvl_ding_sfx )
37452 {
37453 return true;
37454 }
37455 else if ( index == *cvar_skill_newspell_sfx )
37456 {
37457 return true;
37458 }
37459 else
37460 {
37461 for ( auto skill : Player::SkillSheet_t::skillSheetData.skillEntries )
37462 {
37463 if ( index == skill.skillSfx )
37464 {
37465 return true;
37466 }
37467 }
37468 }
37469 return false;
37470}
37471
37472void LevelUpAnimation_t::LevelUp_t::StatUp_t::animateNotification(const int player)
37473{
37474 real_t animspeed = 5.0 / (4.0);
37475 static ConsoleVariable<float> cvar_lvlup_speed("/lvlup_speed", .5);
37476 static ConsoleVariable<float> cvar_lvlup_bounce("/lvlup_bounce", 1.0 /*2.5*/);
37477 static ConsoleVariable<float> cvar_lvlup_animfall("/lvlup_animfall", 0.5/*2.0*/);
37478 static ConsoleVariable<int> cvar_lvlup_ding_volume("/lvlup_ding_volume", 64);
37479 animspeed *= *cvar_lvlup_speed;
37480 int movementAmount = 0;
37481 if ( players[player]->bUseCompactGUIHeight() )
37482 {
37483 movementAmount = 0;
37484 }
37485
37486 if ( ticks != processedOnTick )
37487 {
37488 processedOnTick = ticks;
37489 ++ticksActive;
37490 }
37491
37492 const real_t largestScaling = 2.0;
37493 real_t midScaling = 2.0;
37494 const int normalSize = 36;
37495 const int baseEffectPosX = baseX;
37496 const int baseEffectPosY = baseY;
37497
37498 switch ( notificationState )
37499 {
37500 case STATE_1:
37501 if ( notificationStateInit == STATE_1 )
37502 {
37503 notificationStateInit = STATE_2;
37504 setAnimatePosition(
37505 baseEffectPosX - movementAmount,
37506 baseEffectPosY - movementAmount,
37507 normalSize, normalSize);
37508 }
37509 if ( animateX >= 1.0 )
37510 {
37511 notificationState = STATE_2;
37512 }
37513 animspeed *= .01;
37514 break;
37515 case STATE_2:
37516 if ( notificationStateInit == STATE_2 )
37517 {
37518 notificationStateInit = STATE_3;
37519 setAnimatePosition(
37520 baseEffectPosX - movementAmount - (largestScaling - 1.0) * normalSize / 2,
37521 baseEffectPosY - movementAmount - (largestScaling - 1.0) * normalSize / 2,
37522 normalSize * largestScaling,
37523 normalSize * largestScaling);
37524 }
37525 if ( animateX >= 1.0 )
37526 {
37527 notificationState = STATE_3;
37528 }
37529 animspeed *= 4.0;
37530 break;
37531 case STATE_3:
37532 if ( notificationStateInit == STATE_3 )
37533 {
37534 midScaling = 1.0;
37535 notificationStateInit = STATE_4;
37536 setAnimatePosition(
37537 baseEffectPosX - movementAmount - (midScaling - 1.0) * normalSize / 2,
37538 baseEffectPosY - movementAmount - (midScaling - 1.0) * normalSize / 2,
37539 normalSize * midScaling,
37540 normalSize * midScaling);
37541 }
37542 if ( animateX >= 1.0 )
37543 {
37544 notificationState = STATE_4;
37545 }
37546 animspeed *= 4.0;
37547 break;
37548 case STATE_4:
37549 if ( notificationStateInit == STATE_4 )
37550 {
37551 notificationStateInit = STATE_END;
37552 setAnimatePosition(notificationTargetPosition.x,
37553 notificationTargetPosition.y,
37554 notificationTargetPosition.w,
37555 notificationTargetPosition.h);
37556 }
37557 if ( notificationTargetPosition.x != animateSetpointX
37558 || notificationTargetPosition.y != animateSetpointY
37559 || notificationTargetPosition.w != animateSetpointW
37560 || notificationTargetPosition.h != animateSetpointH )
37561 {
37562 // re update this as our target moved.
37563 setAnimatePosition(notificationTargetPosition.x,
37564 notificationTargetPosition.y,
37565 notificationTargetPosition.w,
37566 notificationTargetPosition.h);
37567 }
37568 if ( animateX >= 1.0 )
37569 {
37570 notificationState = STATE_END;
37571 }
37572 animspeed *= 2.0;
37573 break;
37574 case STATE_END:
37575 {
37576 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
37577 if ( ticksActive >= TICKS_PER_SECOND50 )
37578 {
37579 real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - animCurrentStat)) / (2.5 * *cvar_lvlup_animfall);
37580 animCurrentStat += setpointDiffX;
37581 animCurrentStat = std::min(1.0, animCurrentStat);
37582
37583 if ( animCurrentStat >= 1.0 )
37584 {
37585 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
37586 real_t setpointDiffX = fpsScale * 1.0 / (10.0 * *cvar_lvlup_bounce);
37587 animIncreaseStat += setpointDiffX;
37588 animIncreaseStat = std::min(1.0, animIncreaseStat);
37589 }
37590 }
37591 return;
37592 }
37593 default:
37594 break;
37595 }
37596
37597 real_t oldAngle = animAngle;
37598 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
37599 animAngle += fpsScale * std::max(.1, (1.0 - animAngle)) / (5.0);
37600 animAngle = std::min(1.0, animAngle);
37601 if ( animAngle > 0.0 && oldAngle <= 0.001 )
37602 {
37603 playSound(*cvar_lvl_ding_sfx, *cvar_lvlup_ding_volume);
37604 }
37605 real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - animateX)) / (animspeed);
37606 real_t setpointDiffY = fpsScale * std::max(.1, (1.0 - animateY)) / (animspeed);
37607 real_t setpointDiffW = fpsScale * std::max(.1, (1.0 - animateW)) / (animspeed);
37608 real_t setpointDiffH = fpsScale * std::max(.1, (1.0 - animateH)) / (animspeed);
37609 animateX += setpointDiffX;
37610 animateY += setpointDiffY;
37611 animateX = std::min(1.0, animateX);
37612 animateY = std::min(1.0, animateY);
37613 animateW += setpointDiffW;
37614 animateH += setpointDiffH;
37615 animateW = std::min(1.0, animateW);
37616 animateH = std::min(1.0, animateH);
37617
37618 int destX = animateSetpointX - animateStartX;
37619 int destY = animateSetpointY - animateStartY;
37620 int destW = animateSetpointW - animateStartW;
37621 int destH = animateSetpointH - animateStartH;
37622
37623 pos.x = animateStartX + destX * animateX;
37624 pos.y = animateStartY + destY * animateY;
37625 pos.w = animateStartW + destW * animateW;
37626 pos.h = animateStartH + destH * animateH;
37627}
37628
37629void LevelUpAnimation_t::LevelUp_t::animateTitle(SDL_Rect basePos)
37630{
37631 if ( ticksActive == 0 )
37632 {
37633 titleAnimatePos = basePos;
37634 }
37635
37636 static ConsoleVariable<int> cvar_lvlup_title_ticks("/lvlup_title_ticks", 15);
37637 static ConsoleVariable<int> cvar_lvlup_title_fade_ticks("/lvlup_title_fade_ticks", TICKS_PER_SECOND50);
37638 real_t anim = std::min(1.0, ticksActive / (real_t)*cvar_lvlup_title_ticks);
37639 real_t grow = 1.0;
37640
37641 real_t curvePosition = (LevelUpAnimBreakpoints[anim * (LevelUpAnimBreakpoints.size() - 1)]) / 100.0;
37642
37643 //titleAnimatePos.x = basePos.x - anim * (grow) * basePos.w / 2;
37644 //titleAnimatePos.y = basePos.y - anim * (grow) * basePos.h / 2;
37645 //titleAnimatePos.w = basePos.w + anim * grow * basePos.w;
37646 //titleAnimatePos.h = basePos.h + anim * grow * basePos.h;
37647 titleAnimatePos.x = basePos.x - curvePosition * basePos.w / 2;
37648 titleAnimatePos.y = basePos.y - curvePosition * basePos.h / 2;
37649 titleAnimatePos.w = basePos.w + curvePosition * basePos.w;
37650 titleAnimatePos.h = basePos.h + curvePosition * basePos.h;
37651
37652 if ( ticksActive >= *cvar_lvlup_title_fade_ticks )
37653 {
37654 titleFinishAnim = true;
37655 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
37656 animTitleFade += fpsScale * std::max(.1, (1.0 - animTitleFade)) / (5.0);
37657 animTitleFade = std::min(1.0, animTitleFade);
37658
37659 titleAnimatePos.y += animTitleFade * titleAnimatePos.h;
37660 }
37661}
37662
37663void updateLevelUpFrame(const int player)
37664{
37665 auto& hud_t = players[player]->hud;
37666 if ( !hud_t.levelupFrame )
37667 {
37668 createLevelUpFrame(player);
37669 }
37670
37671 if ( !hud_t.levelupFrame || !hud_t.hudFrame )
37672 {
37673 return;
37674 }
37675
37676 if ( !players[player]->isLocalPlayer() || hud_t.hudFrame->isDisabled() )
37677 {
37678 hud_t.levelupFrame->setDisabled(true);
37679 return;
37680 }
37681
37682 if ( gamePaused && multiplayer == SINGLE0 )
37683 {
37684 return;
37685 }
37686
37687 auto frame = hud_t.levelupFrame;
37688 auto& lvlUpAnimation = levelUpAnimation[player];
37689
37690 static ConsoleVariable<int> cvar_lvlup_debug("/lvlup_debug", 0);
37691 if ( *cvar_lvlup_debug > 0 )
37692 {
37693 std::vector<int> statPicks;
37694 std::vector<unsigned int> statWeight = { 1, 1, 1, 1, 1, 1 };
37695 if ( *cvar_lvlup_debug != 1 )
37696 {
37697 for ( int i = 0; i < std::min(*cvar_lvlup_debug, NUMSTATS); ++i )
37698 {
37699 int stat = local_rng.discrete(statWeight.data(), statWeight.size());
37700 statWeight[stat] = 0;
37701 statPicks.push_back(stat);
37702 }
37703 }
37704 else
37705 {
37706 for ( int i = 0; i < 3; ++i )
37707 {
37708 int stat = local_rng.discrete(statWeight.data(), statWeight.size());
37709 statWeight[stat] = 0;
37710 statPicks.push_back(stat);
37711 }
37712 }
37713 int currentStat = 0;
37714 int increaseStat = 1;
37715 std::vector<LevelUpAnimation_t::LevelUp_t::StatUp_t> StatUps;
37716 for ( int i = 0; i < statPicks.size(); ++i )
37717 {
37718 if ( *cvar_lvlup_debug != 1 )
37719 {
37720 increaseStat = local_rng.rand() % 2;
37721 }
37722 switch ( statPicks[i] )
37723 {
37724 case STAT_STR:
37725 currentStat = stats[player]->STR;
37726 break;
37727 case STAT_DEX:
37728 currentStat = stats[player]->DEX;
37729 break;
37730 case STAT_CON:
37731 currentStat = stats[player]->CON;
37732 break;
37733 case STAT_INT:
37734 currentStat = stats[player]->INT;
37735 break;
37736 case STAT_PER:
37737 currentStat = stats[player]->PER;
37738 break;
37739 case STAT_CHR:
37740 currentStat = stats[player]->CHR;
37741 break;
37742 default:
37743 break;
37744 }
37745
37746 StatUps.push_back(LevelUpAnimation_t::LevelUp_t::StatUp_t(statPicks[i], currentStat, increaseStat));
37747 }
37748
37749 /*lvlUpAnimation.lvlUps.clear();*/
37750 lvlUpAnimation.addLevelUp(stats[player]->LVL, 1, StatUps);
37751
37752 for ( auto& lvlUp : lvlUpAnimation.lvlUps )
37753 {
37754 messagePlayer(player, MESSAGE_DEBUG, "Lvl: [%d %d]", lvlUp.currentLvl, lvlUp.increaseLvl);
37755 for ( auto& statUp : lvlUp.statUps )
37756 {
37757 messagePlayer(player, MESSAGE_DEBUG, "Stat: %d: [%d %d]", statUp.whichStat, statUp.currentStat, statUp.increaseStat);
37758 }
37759 }
37760 *cvar_lvlup_debug = 0;
37761 }
37762
37763 if ( lvlUpAnimation.lvlUps.empty() )
37764 {
37765 hud_t.levelupFrame->setDisabled(true);
37766 return;
37767 }
37768
37769 if ( lvlUpAnimation.lvlUps.front().expired )
37770 {
37771 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
37772 lvlUpAnimation.lvlUps.front().fadeout += fpsScale * std::max(.1, (1.0 - lvlUpAnimation.lvlUps.front().fadeout)) / (5.0);
37773 lvlUpAnimation.lvlUps.front().fadeout = std::min(1.0, lvlUpAnimation.lvlUps.front().fadeout);
37774 hud_t.levelupFrame->setOpacity(hud_t.hudFrame->getOpacity()
37775 * (1.0 - lvlUpAnimation.lvlUps.front().fadeout));
37776 if ( lvlUpAnimation.lvlUps.front().fadeout >= 1.0 )
37777 {
37778 lvlUpAnimation.lvlUps.pop_front();
37779 }
37780 }
37781
37782 if ( lvlUpAnimation.lvlUps.empty() )
37783 {
37784 hud_t.levelupFrame->setDisabled(true);
37785 return;
37786 }
37787
37788 auto& lvlUp = lvlUpAnimation.lvlUps.front();
37789
37790 static ConsoleVariable<int> cvar_lvlup_angle("/lvlup_angle", 16);
37791 static ConsoleVariable<int> cvar_lvlup_falldist("/lvlup_falldist", 64);
37792 static ConsoleVariable<int> cvar_lvlup_currentstatY("/lvlup_currentstatY", -10);
37793 static ConsoleVariable<int> cvar_lvlup_increasestatY("/lvlup_increasestatY", 0);
37794
37795 if ( !lvlUp.expired )
37796 {
37797 hud_t.levelupFrame->setOpacity(hud_t.hudFrame->getOpacity());
37798 }
37799 hud_t.levelupFrame->setDisabled(false);
37800 const int frameWidth = 600;
37801
37802
37803 auto lvlupImg = hud_t.levelupFrame->findImage("lvl up img");
37804 lvlupImg->disabled = false;
37805 if ( auto imgGet = Image::get(lvlupImg->path.c_str()) )
37806 {
37807 lvlupImg->pos.w = imgGet->getWidth();
37808 lvlupImg->pos.h = imgGet->getHeight();
37809 lvlupImg->pos.x = frameWidth / 2 - lvlupImg->pos.w / 2;
37810 lvlupImg->pos.y = 0;
37811 }
37812
37813 SDL_Rect statsFramePos{ frameWidth / 2, 0, 0, 200 };
37814 auto statsFrame = hud_t.levelupFrame->findFrame("stats");
37815 statsFrame->setDisabled(true);
37816
37817 lvlUp.animateTitle(lvlupImg->pos);
37818 lvlupImg->pos = lvlUp.titleAnimatePos;
37819 lvlupImg->color = makeColor(255, 255, 255, 255 * (1.0 - lvlUp.animTitleFade));
37820 if ( lvlUp.titleFinishAnim )
37821 {
37822 statsFrame->setDisabled(false);
37823 }
37824
37825 int index = 0;
37826 SDL_Rect statFramePos{ 0, 0, 0, 200 };
37827 const int statSpacing = 64;
37828 int statStartX = 100;
37829 std::vector<int> statPosX;
37830 auto prevNotificationState = LevelUpAnimation_t::LevelUp_t::StatUp_t::NotificationStates_t::STATE_END;
37831 for ( auto statFrame : statsFrame->getFrames() )
37832 {
37833 if ( statsFrame->isDisabled() )
37834 {
37835 statFrame->setDisabled(true);
37836 continue;
37837 }
37838 int currentIndex = index;
37839 ++index;
37840 if ( currentIndex >= lvlUp.statUps.size() )
37841 {
37842 statFrame->setDisabled(true);
37843 continue;
37844 }
37845 auto& statUp = lvlUp.statUps[currentIndex];
37846 auto statImg = statFrame->findImage("stat img");
37847 statImg->path = "";
37848 statImg->disabled = true;
37849 switch ( statUp.whichStat )
37850 {
37851 case STAT_STR:
37852 //statImg->path = Player::CharacterSheet_t::getHoverTextString("icon_str_path");
37853 statImg->path = "images/ui/HUD/HUD_StatUp_STR_00.png";
37854 break;
37855 case STAT_DEX:
37856 statImg->path = "images/ui/HUD/HUD_StatUp_DEX_00.png";
37857 break;
37858 case STAT_CON:
37859 statImg->path = "images/ui/HUD/HUD_StatUp_CON_00.png";
37860 break;
37861 case STAT_INT:
37862 statImg->path = "images/ui/HUD/HUD_StatUp_INT_00.png";
37863 break;
37864 case STAT_PER:
37865 statImg->path = "images/ui/HUD/HUD_StatUp_PER_00.png";
37866 break;
37867 case STAT_CHR:
37868 statImg->path = "images/ui/HUD/HUD_StatUp_CHA_00.png";
37869 break;
37870 default:
37871 break;
37872 }
37873
37874 char buf[32] = "";
37875
37876 if ( statImg->path == "" ) { continue; }
37877
37878 if ( auto imgGet = Image::get(statImg->path.c_str()) )
37879 {
37880 statImg->pos.w = imgGet->getWidth();
37881 statImg->pos.h = imgGet->getHeight();
37882 statImg->disabled = false;
37883 }
37884 statImg->pos.x = 32;
37885 int baseImgY = 48;
37886 if ( players[player]->bUseCompactGUIHeight() )
37887 {
37888 baseImgY = 40;
37889 }
37890 statImg->pos.y = baseImgY - statImg->pos.h / 2;
37891
37892 snprintf(buf, sizeof(buf), "%+d", statUp.increaseStat);
37893 auto statIncreaseTxt = statFrame->findField("stat increase");
37894 statIncreaseTxt->setDisabled(statUp.increaseStat == 0);
37895 statIncreaseTxt->setText(buf);
37896 if ( auto textGet = statIncreaseTxt->getTextObject() )
37897 {
37898 SDL_Rect pos;
37899 pos.x = statImg->pos.x + statImg->pos.w + 8;
37900 pos.y = baseImgY - 8;
37901 pos.w = std::max(40, (int)textGet->getWidth());
37902 pos.h = std::max(24, (int)textGet->getHeight());
37903
37904 statIncreaseTxt->setColor(makeColor(255, 255, 255, (1.0 - statUp.animCurrentStat) * statUp.animAngle * 255));
37905 pos.x += -*cvar_lvlup_angle * cos(PI3.14159265358979323846 / 2 + (3 * statUp.animAngle * (PI3.14159265358979323846 / 2)));
37906 pos.y += (*cvar_lvlup_angle) - (*cvar_lvlup_angle * sin(PI3.14159265358979323846 / 2 + (3 * statUp.animAngle * (PI3.14159265358979323846 / 2))));
37907 pos.y += *cvar_lvlup_increasestatY;
37908 statIncreaseTxt->setSize(pos);
37909 }
37910
37911 snprintf(buf, sizeof(buf), "%d", statUp.currentStat + statUp.increaseStat);
37912 auto statCurrentTxt = statFrame->findField("stat current");
37913 statCurrentTxt->setDisabled(!(statUp.ticksActive >= TICKS_PER_SECOND50 * 1));
37914 statCurrentTxt->setText(buf);
37915 if ( auto textGet = statCurrentTxt->getTextObject() )
37916 {
37917 SDL_Rect pos;
37918 pos.w = textGet->getWidth();
37919 pos.h = textGet->getHeight();
37920 pos.x = statImg->pos.x + statImg->pos.w / 2 - pos.w / 2;
37921 pos.y = statImg->pos.y + statImg->pos.h + 4;
37922 pos.y += *cvar_lvlup_currentstatY;
37923 if ( statUp.increaseStat != 0 )
37924 {
37925 pos.y += -((1.0 - statUp.animCurrentStat) * *cvar_lvlup_falldist);
37926 pos.y += -4 + 4 * (cos(statUp.animIncreaseStat * 2 * PI3.14159265358979323846));
37927 }
37928 statCurrentTxt->setSize(pos);
37929
37930 Uint8 r, g, b, a;
37931 getColor(0xFFFFFFFF, &r, &g, &b, &a);
37932 statCurrentTxt->setColor(makeColor(r, g, b, statUp.animCurrentStat * a));
37933 }
37934
37935 statFramePos.w = 120;
37936 statFrame->setSize(statFramePos);
37937
37938 statPosX.push_back(statFramePos.x + statImg->pos.x + statImg->pos.w / 2);
37939 statFramePos.x += statSpacing;
37940
37941 if ( !statUp.init && (prevNotificationState == LevelUpAnimation_t::LevelUp_t::StatUp_t::NotificationStates_t::STATE_END
37942 || prevNotificationState >= LevelUpAnimation_t::LevelUp_t::StatUp_t::NotificationStates_t::STATE_3) )
37943 {
37944 statUp.notificationTargetPosition.x = statImg->pos.x;
37945 statUp.notificationTargetPosition.y = statImg->pos.y;
37946 statUp.notificationTargetPosition.w = statImg->pos.w;
37947 statUp.notificationTargetPosition.h = statImg->pos.h;
37948 statUp.baseX = statImg->pos.x;
37949 statUp.baseY = statImg->pos.y;
37950 statUp.pos.x = statUp.baseX;
37951 statUp.pos.y = statUp.baseY;
37952 statUp.pos.w = statImg->pos.w;
37953 statUp.pos.h = statImg->pos.h;
37954 statUp.init = true;
37955 }
37956
37957 if ( statUp.init )
37958 {
37959 statUp.animateNotification(player);
37960 statFrame->setDisabled(false);
37961 }
37962 else
37963 {
37964 statFrame->setDisabled(true);
37965 }
37966 statImg->pos = statUp.pos;
37967 statsFramePos.w = statFramePos.x + statFramePos.w;
37968 statStartX += statSpacing;
37969
37970 prevNotificationState = statUp.notificationState;
37971 }
37972
37973 int midpoint = 0;
37974 if ( statPosX.size() > 1 )
37975 {
37976 if ( statPosX.size() % 2 == 1 )
37977 {
37978 midpoint = statPosX[size_t(statPosX.size() / 2)];
37979 }
37980 else
37981 {
37982 size_t midIndex1 = std::max((size_t)0, (statPosX.size() / 2) - 1);
37983 size_t midIndex2 = (statPosX.size() / 2);
37984 midpoint = statPosX[midIndex1] + (statPosX[midIndex2] - statPosX[midIndex1]) / 2;
37985 }
37986 statsFramePos.x -= midpoint;
37987 }
37988 else if ( statPosX.size() == 1 )
37989 {
37990 statsFramePos.x -= statPosX[0];
37991 }
37992 else
37993 {
37994 statsFramePos.x -= statsFramePos.w / 2;
37995 }
37996 if ( statsFramePos.x % 2 == 1 )
37997 {
37998 ++statsFramePos.x;
37999 }
38000 statsFrame->setSize(statsFramePos);
38001 if ( ticks != lvlUp.processedOnTick )
38002 {
38003 lvlUp.processedOnTick = ticks;
38004 ++lvlUp.ticksActive;
38005 }
38006
38007 static ConsoleVariable<int> cvar_lvlup_framey("/lvlup_framey", 16);
38008 SDL_Rect levelUpFramePos{ hud_t.hudFrame->getSize().w / 2 - frameWidth / 2, *cvar_lvlup_framey,
38009 frameWidth, std::max(lvlupImg->pos.y + lvlupImg->pos.h, statsFramePos.y + statsFramePos.h)};
38010 levelUpFramePos.x += players[player]->camera_virtualx1();
38011 levelUpFramePos.y += players[player]->camera_virtualy1();
38012 levelUpFramePos.y += lvlUp.fadeout * *cvar_lvlup_falldist;
38013 hud_t.levelupFrame->setSize(levelUpFramePos);
38014
38015 if ( lvlUp.ticksActive >= lvlUp.ticksToLive )
38016 {
38017 lvlUp.expired = true;
38018 }
38019}
38020
38021SkillUpAnimation_t skillUpAnimation[MAXPLAYERS4];
38022
38023void createSkillUpFrame(const int player)
38024{
38025 auto& hud_t = players[player]->hud;
38026 hud_t.skillupFrame = gameUIFrame[player]->addFrame("skillup");
38027 hud_t.skillupFrame->setHollow(true);
38028 hud_t.skillupFrame->setInheritParentFrameOpacity(false);
38029 hud_t.skillupFrame->setOpacity(100.0);
38030 hud_t.skillupFrame->setDisabled(true);
38031 hud_t.skillupFrame->setOwner(player);
38032
38033 auto skillsFrame = hud_t.skillupFrame->addFrame("skills");
38034 skillsFrame->setDisabled(true);
38035 skillsFrame->setHollow(true);
38036 char name[32];
38037 std::string font = "fonts/pixelmix.ttf#16#2";
38038
38039 auto skillFrame = skillsFrame->addFrame("skill");
38040 skillFrame->setDisabled(true);
38041 skillFrame->setHollow(true);
38042
38043 skillFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "skill bg img");
38044 skillFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "skill bg cap img");
38045 skillFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "skill border img");
38046 skillFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "skill img");
38047 skillFrame->addImage(SDL_Rect{ 0, 0, 0, 0 }, 0xFFFFFFFF, "", "skill gleam");
38048
38049 auto skillCurrentTxt = skillFrame->addField("skill current", 32);
38050 skillCurrentTxt->setFont(font.c_str());
38051 skillCurrentTxt->setHJustify(Field::justify_t::RIGHT);
38052 skillCurrentTxt->setVJustify(Field::justify_t::TOP);
38053 skillCurrentTxt->setText("0");
38054
38055 auto skillCurrentOldTxt = skillFrame->addField("skill current old", 32);
38056 skillCurrentOldTxt->setFont(font.c_str());
38057 skillCurrentOldTxt->setHJustify(Field::justify_t::RIGHT);
38058 skillCurrentOldTxt->setVJustify(Field::justify_t::TOP);
38059 skillCurrentOldTxt->setText("0");
38060
38061 auto skillIncreaseTxt = skillFrame->addField("skill increase", 32);
38062 skillIncreaseTxt->setFont(font.c_str());
38063 skillIncreaseTxt->setHJustify(Field::justify_t::LEFT);
38064 skillIncreaseTxt->setVJustify(Field::justify_t::TOP);
38065 skillIncreaseTxt->setText("0");
38066
38067 std::string font2 = "fonts/pixel_maz.ttf#32#2";
38068 auto skillNameTxt = skillFrame->addField("skill name txt", 64);
38069 skillNameTxt->setFont(font2.c_str());
38070 skillNameTxt->setHJustify(Field::justify_t::RIGHT);
38071 skillNameTxt->setVJustify(Field::justify_t::TOP);
38072 skillNameTxt->setText("");
38073}
38074
38075void SkillUpAnimation_t::SkillUp_t::setAnimatePosition(int destx, int desty, int destw, int desth)
38076{
38077 animateStartX = pos.x;
38078 animateStartY = pos.y;
38079 animateStartW = pos.w;
38080 animateStartH = pos.h;
38081 animateSetpointX = destx;
38082 animateSetpointY = desty;
38083 animateSetpointW = destw;
38084 animateSetpointH = desth;
38085 animateX = 0.0;
38086 animateY = 0.0;
38087 animateW = 0.0;
38088 animateH = 0.0;
38089}
38090
38091void SkillUpAnimation_t::SkillUp_t::setAnimatePosition(int destx, int desty)
38092{
38093 animateStartX = pos.x;
38094 animateStartY = pos.y;
38095 animateStartW = pos.w;
38096 animateStartH = pos.h;
38097 animateSetpointX = destx;
38098 animateSetpointY = desty;
38099 animateSetpointW = 0;
38100 animateSetpointH = 0;
38101 animateX = 0.0;
38102 animateY = 0.0;
38103 animateW = 0.0;
38104 animateH = 0.0;
38105}
38106
38107int SkillUpAnimation_t::SkillUp_t::getIconNominalSize()
38108{
38109 return isSpell ? 32 : 24;
38110}
38111
38112void SkillUpAnimation_t::SkillUp_t::animateNotification(const int player)
38113{
38114 real_t animspeed = 5.0 / (4.0);
38115 static ConsoleVariable<float> cvar_skillup_speed("/skillup_speed", .5);
38116 static ConsoleVariable<float> cvar_skillup_bounce("/skillup_bounce", 1.0 /*2.5*/);
38117 static ConsoleVariable<float> cvar_skillup_animfall("/skillup_animfall", 0.5/*2.0*/);
38118 static ConsoleVariable<int> cvar_skillup_increase_delay("/skillup_increase_delay", 50);
38119 static ConsoleVariable<int> cvar_skillup_ding_volume("/skillup_ding_volume", 32);
38120 animspeed *= *cvar_skillup_speed;
38121 int movementAmount = 0;
38122 if ( players[player]->bUseCompactGUIHeight() )
38123 {
38124 movementAmount = 0;
38125 }
38126
38127 bool newtick = ticks != processedOnTick;
38128 if ( newtick )
38129 {
38130 processedOnTick = ticks;
38131 ++ticksActive;
38132 }
38133
38134 const real_t largestScaling = 2.0;
38135 real_t midScaling = 2.0;
38136 const int normalSize = getIconNominalSize();
38137 const int baseEffectPosX = baseX;
38138 const int baseEffectPosY = baseY;
38139
38140 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
38141 if ( notificationState >= STATE_4 )
38142 {
38143 real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - animBackground)) / (2.5);
38144 animBackground += setpointDiffX;
38145 animBackground = std::min(1.0, animBackground);
38146 }
38147
38148 real_t oldAngle = animAngle;
38149 animAngle += fpsScale * std::max(.1, (1.0 - animAngle)) / (5.0);
38150 animAngle = std::min(1.0, animAngle);
38151 if ( newtick && ticksActive == *cvar_skillup_increase_delay )
38152 {
38153 if ( !isSpell )
38154 {
38155 playSound(*cvar_skill_ding_sfx + local_rng.rand() % 4, *cvar_skillup_ding_volume);
38156 }
38157 }
38158
38159 switch ( notificationState )
38160 {
38161 case STATE_1:
38162 if ( notificationStateInit == STATE_1 )
38163 {
38164 notificationStateInit = STATE_2;
38165 setAnimatePosition(
38166 baseEffectPosX - movementAmount,
38167 baseEffectPosY - movementAmount,
38168 normalSize, normalSize);
38169 }
38170 if ( animateX >= 1.0 )
38171 {
38172 notificationState = STATE_2;
38173 }
38174 animspeed *= .01;
38175 break;
38176 case STATE_2:
38177 if ( notificationStateInit == STATE_2 )
38178 {
38179 notificationStateInit = STATE_3;
38180 setAnimatePosition(
38181 baseEffectPosX - movementAmount - (largestScaling - 1.0) * normalSize / 2,
38182 baseEffectPosY - movementAmount - (largestScaling - 1.0) * normalSize / 2,
38183 normalSize * largestScaling,
38184 normalSize * largestScaling);
38185 }
38186 if ( animateX >= 1.0 )
38187 {
38188 notificationState = STATE_3;
38189 }
38190 animspeed *= 4.0;
38191 break;
38192 case STATE_3:
38193 if ( notificationStateInit == STATE_3 )
38194 {
38195 midScaling = 1.0;
38196 notificationStateInit = STATE_4;
38197 setAnimatePosition(
38198 baseEffectPosX - movementAmount - (midScaling - 1.0) * normalSize / 2,
38199 baseEffectPosY - movementAmount - (midScaling - 1.0) * normalSize / 2,
38200 normalSize * midScaling,
38201 normalSize * midScaling);
38202 }
38203 if ( animateX >= 1.0 )
38204 {
38205 notificationState = STATE_4;
38206 }
38207 animspeed *= 4.0;
38208 break;
38209 case STATE_4:
38210 if ( notificationStateInit == STATE_4 )
38211 {
38212 notificationStateInit = STATE_END;
38213 setAnimatePosition(notificationTargetPosition.x,
38214 notificationTargetPosition.y,
38215 notificationTargetPosition.w,
38216 notificationTargetPosition.h);
38217 }
38218 if ( notificationTargetPosition.x != animateSetpointX
38219 || notificationTargetPosition.y != animateSetpointY
38220 || notificationTargetPosition.w != animateSetpointW
38221 || notificationTargetPosition.h != animateSetpointH )
38222 {
38223 // re update this as our target moved.
38224 setAnimatePosition(notificationTargetPosition.x,
38225 notificationTargetPosition.y,
38226 notificationTargetPosition.w,
38227 notificationTargetPosition.h);
38228 }
38229 if ( animateX >= 1.0 )
38230 {
38231 notificationState = STATE_END;
38232 }
38233 animspeed *= 2.0;
38234 break;
38235 case STATE_END:
38236 {
38237 if ( ticksActive >= TICKS_PER_SECOND50 )
38238 {
38239 real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - animCurrentStat)) / (2.5 * *cvar_skillup_animfall);
38240 animCurrentStat += setpointDiffX;
38241 animCurrentStat = std::min(1.0, animCurrentStat);
38242
38243 if ( animCurrentStat >= 1.0 )
38244 {
38245 real_t setpointDiffX = fpsScale * 1.0 / (10.0 * *cvar_skillup_bounce);
38246 animIncreaseStat += setpointDiffX;
38247 animIncreaseStat = std::min(1.0, animIncreaseStat);
38248 }
38249 }
38250 return;
38251 }
38252 default:
38253 break;
38254 }
38255
38256 real_t setpointDiffX = fpsScale * std::max(.1, (1.0 - animateX)) / (animspeed);
38257 real_t setpointDiffY = fpsScale * std::max(.1, (1.0 - animateY)) / (animspeed);
38258 real_t setpointDiffW = fpsScale * std::max(.1, (1.0 - animateW)) / (animspeed);
38259 real_t setpointDiffH = fpsScale * std::max(.1, (1.0 - animateH)) / (animspeed);
38260 animateX += setpointDiffX;
38261 animateY += setpointDiffY;
38262 animateX = std::min(1.0, animateX);
38263 animateY = std::min(1.0, animateY);
38264 animateW += setpointDiffW;
38265 animateH += setpointDiffH;
38266 animateW = std::min(1.0, animateW);
38267 animateH = std::min(1.0, animateH);
38268
38269 int destX = animateSetpointX - animateStartX;
38270 int destY = animateSetpointY - animateStartY;
38271 int destW = animateSetpointW - animateStartW;
38272 int destH = animateSetpointH - animateStartH;
38273
38274 pos.x = animateStartX + destX * animateX;
38275 pos.y = animateStartY + destY * animateY;
38276 pos.w = animateStartW + destW * animateW;
38277 pos.h = animateStartH + destH * animateH;
38278}
38279
38280SkillUpAnimation_t::SkillUp_t& SkillUpAnimation_t::getSkillUpToDisplay()
38281{
38282 return skillUps.at(getSkillUpIndexToDisplay());
38283}
38284
38285size_t SkillUpAnimation_t::getSkillUpIndexToDisplay()
38286{
38287 size_t index = 0;
38288 std::priority_queue<std::pair<int, size_t>> priority;
38289 for ( auto& skillUp : skillUps )
38290 {
38291 if ( skillUp.init )
38292 {
38293 return index;
38294 }
38295 if ( skillUp.isSpell )
38296 {
38297 priority.push(std::make_pair(15, index));
38298 }
38299 else
38300 {
38301 switch ( skillUp.whichSkill )
38302 {
38303 case PRO_RANGED:
38304 case PRO_SWORD:
38305 case PRO_MACE:
38306 case PRO_AXE:
38307 case PRO_POLEARM:
38308 case PRO_SHIELD:
38309 case PRO_UNARMED:
38310 priority.push(std::make_pair(10, index));
38311 break;
38312 case PRO_MAGIC:
38313 case PRO_SPELLCASTING:
38314 priority.push(std::make_pair(5, index));
38315 break;
38316 case PRO_STEALTH:
38317 priority.push(std::make_pair(4, index));
38318 break;
38319 case PRO_LOCKPICKING:
38320 case PRO_TRADING:
38321 case PRO_LEADERSHIP:
38322 case PRO_ALCHEMY:
38323 priority.push(std::make_pair(2, index));
38324 break;
38325 case PRO_SWIMMING:
38326 priority.push(std::make_pair(1, index));
38327 break;
38328 case PRO_APPRAISAL:
38329 default:
38330 priority.push(std::make_pair(0, index));
38331 break;
38332 }
38333 }
38334 ++index;
38335 }
38336 return priority.top().second;
38337}
38338
38339void SkillUpAnimation_t::addSpellLearned(const int _spellID)
38340{
38341 skillUps.push_back(SkillUp_t(_spellID));
38342 skillUps.at(skillUps.size() - 1).ticksToLive = 5 * TICKS_PER_SECOND50;
38343}
38344
38345void SkillUpAnimation_t::addSkillUp(const int _numSkill, const int _currentSkill, const int _increaseSkill)
38346{
38347 if ( _increaseSkill <= 0 )
38348 {
38349 return;
38350 }
38351
38352 for ( auto& s : skillUps )
38353 {
38354 if ( s.whichSkill != _numSkill )
38355 {
38356 continue;
38357 }
38358 if ( s.expired )
38359 {
38360 continue;
38361 }
38362 int diff = std::max(0, (_currentSkill + _increaseSkill) - (s.currentSkill + s.increaseSkill));
38363 if ( diff > 0 )
38364 {
38365 if ( s.ticksActive >= TICKS_PER_SECOND50 )
38366 {
38367 // update current value
38368 s.currentSkill = _currentSkill;
38369 s.increaseSkill = _increaseSkill;
38370 }
38371 else
38372 {
38373 s.increaseSkill += diff;
38374 }
38375 s.ticksActive = 0;
38376 s.animAngle = 0.0;
38377 s.animCurrentStat = 0.0;
38378 s.animIncreaseStat = 0.0;
38379 return;
38380 }
38381 else
38382 {
38383 return;
38384 }
38385 }
38386
38387 skillUps.push_back(SkillUp_t(_numSkill, _currentSkill, _increaseSkill));
38388 Uint32 ticksToLive = 3 * TICKS_PER_SECOND50;
38389 switch ( _numSkill )
38390 {
38391 case PRO_RANGED:
38392 case PRO_SWORD:
38393 case PRO_MACE:
38394 case PRO_AXE:
38395 case PRO_POLEARM:
38396 case PRO_SHIELD:
38397 case PRO_UNARMED:
38398 ticksToLive = 4 * TICKS_PER_SECOND50;
38399 break;
38400 case PRO_STEALTH:
38401 ticksToLive = 4 * TICKS_PER_SECOND50;
38402 break;
38403 case PRO_MAGIC:
38404 case PRO_SPELLCASTING:
38405 ticksToLive = 2 * TICKS_PER_SECOND50;
38406 break;
38407 case PRO_LOCKPICKING:
38408 case PRO_TRADING:
38409 case PRO_LEADERSHIP:
38410 case PRO_ALCHEMY:
38411 ticksToLive = 3 * TICKS_PER_SECOND50;
38412 break;
38413 case PRO_SWIMMING:
38414 ticksToLive = 2 * TICKS_PER_SECOND50;
38415 break;
38416 case PRO_APPRAISAL:
38417 default:
38418 ticksToLive = 2 * TICKS_PER_SECOND50;
38419 break;
38420 }
38421 skillUps.at(skillUps.size() - 1).ticksToLive = ticksToLive;
38422}
38423
38424void updateSkillUpFrame(const int player)
38425{
38426 auto& hud_t = players[player]->hud;
38427 if ( !hud_t.skillupFrame )
38428 {
38429 createSkillUpFrame(player);
38430 }
38431
38432 if ( !hud_t.skillupFrame || !hud_t.hudFrame )
38433 {
38434 return;
38435 }
38436
38437 if ( !players[player]->isLocalPlayer() || hud_t.hudFrame->isDisabled() )
38438 {
38439 hud_t.skillupFrame->setDisabled(true);
38440 return;
38441 }
38442
38443 if ( gamePaused && multiplayer == SINGLE0 )
38444 {
38445 return;
38446 }
38447
38448 auto frame = hud_t.skillupFrame;
38449 auto& skillUpAnim = skillUpAnimation[player];
38450
38451 //if ( enableDebugKeys && keystatus[SDLK_g] )
38452 //{
38453 // keystatus[SDLK_g] = 0;
38454
38455 // int skill = PRO_APPRAISAL; local_rng.rand() % NUMPROFICIENCIES;
38456 // int currentSkill = stats[player]->PROFICIENCIES[skill];
38457 // int increaseSkill = 1;
38458 // skillUpAnim.addSkillUp(skill, currentSkill, increaseSkill);
38459 // //skillUpAnim.skillUps.push_back(SkillUpAnimation_t::SkillUp_t(skill, currentSkill, increaseSkill));
38460 // ++stats[player]->PROFICIENCIES[skill];
38461 //}
38462
38463 //if ( enableDebugKeys && keystatus[SDLK_h] )
38464 //{
38465 // keystatus[SDLK_h] = 0;
38466
38467 // skillUpAnim.addSpellLearned(SPELL_FORCEBOLT + local_rng.rand() % (NUM_SPELLS - 1));
38468 //}
38469
38470 if ( skillUpAnim.skillUps.empty() || levelUpAnimation[player].lvlUps.size() > 0 )
38471 {
38472 if ( levelUpAnimation[player].lvlUps.size() > 0 )
38473 {
38474 skillUpAnim.animFrameFadeIn = 0.0;
38475 }
38476 hud_t.skillupFrame->setDisabled(true);
38477 return;
38478 }
38479
38480 if ( players[player]->bUseCompactGUIWidth() && players[player]->gui_mode != GUI_MODE_NONE )
38481 {
38482 hud_t.skillupFrame->setDisabled(true);
38483 return;
38484 }
38485
38486 auto& skillUpCheck = skillUpAnim.getSkillUpToDisplay();
38487 const real_t fpsScale = getFPSScale(50.0); // ported from 50Hz
38488 if ( skillUpCheck.expired )
38489 {
38490 skillUpCheck.fadeout += fpsScale * std::max(.1, (1.0 - skillUpCheck.fadeout)) / (5.0);
38491 skillUpCheck.fadeout = std::min(1.0, skillUpCheck.fadeout);
38492 hud_t.skillupFrame->setOpacity(hud_t.hudFrame->getOpacity()
38493 * (1.0 - skillUpCheck.fadeout) * skillUpAnim.animFrameFadeIn);
38494 if ( skillUpCheck.fadeout >= 1.0 )
38495 {
38496 skillUpAnim.skillUps.erase(skillUpAnim.skillUps.begin() + skillUpAnim.getSkillUpIndexToDisplay());
38497 }
38498 }
38499
38500 if ( skillUpAnim.skillUps.empty() )
38501 {
38502 hud_t.skillupFrame->setDisabled(true);
38503 return;
38504 }
38505
38506 auto& skillUp = skillUpAnim.getSkillUpToDisplay();
38507
38508 static ConsoleVariable<int> cvar_skillup_angle("/skillup_angle", 16);
38509 static ConsoleVariable<int> cvar_skillup_falldist("/skillup_falldist", 64);
38510 static ConsoleVariable<int> cvar_skillup_currentstatY("/skillup_currentstatY", -10);
38511 static ConsoleVariable<int> cvar_skillup_increasestatY("/skillup_increasestatY", 0);
38512
38513 if ( !skillUp.expired )
38514 {
38515 skillUpAnim.animFrameFadeIn += fpsScale * std::max(.1, (1.0 - skillUpAnim.animFrameFadeIn)) / (5.0);
38516 skillUpAnim.animFrameFadeIn = std::min(1.0, skillUpAnim.animFrameFadeIn);
38517 hud_t.skillupFrame->setOpacity(hud_t.hudFrame->getOpacity() * skillUpAnim.animFrameFadeIn);
38518 }
38519 hud_t.skillupFrame->setDisabled(false);
38520 const int frameWidth = 600;
38521
38522 SDL_Rect skillsFramePos{ frameWidth / 2, 0, 0, 200 };
38523 auto skillsFrame = hud_t.skillupFrame->findFrame("skills");
38524 skillsFrame->setDisabled(false);
38525
38526 int index = 0;
38527 SDL_Rect skillFramePos{ 0, 0, 0, 400 };
38528 const int skillSpacing = 64;
38529 int skillStartX = 100;
38530 std::vector<int> skillPosX;
38531 auto prevNotificationState = SkillUpAnimation_t::SkillUp_t::NotificationStates_t::STATE_END;
38532 for ( auto skillFrame : skillsFrame->getFrames() )
38533 {
38534 if ( skillsFrame->isDisabled() )
38535 {
38536 skillFrame->setDisabled(true);
38537 continue;
38538 }
38539 if ( skillUp.preDelayTicks > 0 )
38540 {
38541 continue;
38542 }
38543 auto skillImg = skillFrame->findImage("skill img");
38544 auto skillBorderImg = skillFrame->findImage("skill border img");
38545 auto skillBgImg = skillFrame->findImage("skill bg img");
38546 auto skillBgCapImg = skillFrame->findImage("skill bg cap img");
38547 skillBgCapImg->disabled = true;
38548 skillImg->path = "";
38549 skillImg->disabled = true;
38550 skillBorderImg->disabled = true;
38551 skillBgImg->disabled = true;
38552 int skillsheetIndex = -1;
38553 auto skillCurrentTxt = skillFrame->findField("skill current");
38554 auto skillCurrentOldTxt = skillFrame->findField("skill current old");
38555
38556 if ( skillUp.isSpell )
38557 {
38558 skillCurrentTxt->setDisabled(true);
38559 }
38560 else
38561 {
38562 char buf[32] = "";
38563 snprintf(buf, sizeof(buf), "%d", skillUp.currentSkill + skillUp.increaseSkill);
38564 skillCurrentTxt->setDisabled(!(skillUp.ticksActive >= TICKS_PER_SECOND50 * 1));
38565 skillCurrentTxt->setText(buf);
38566 }
38567
38568 if ( skillUp.isSpell )
38569 {
38570 node_t* spellImageNode = ItemTooltips.getSpellNodeFromSpellID(skillUp.spellID);
38571 if ( spellImageNode )
38572 {
38573 if ( string_t* string = (string_t*)spellImageNode->element )
38574 {
38575 skillImg->path = string->data;
38576 }
38577 skillCurrentTxt->setColor(Player::SkillSheet_t::skillSheetData.defaultTextColor);
38578 skillCurrentOldTxt->setColor(hudColors.itemContextMenuHeadingText);
38579 skillBorderImg->path = "#*images/ui/HUD/HUD_NewSpell_Border_00.png";
38580 skillBgImg->path = "*images/ui/HUD/HUD_NewSpell_BG_00.png";
38581 }
38582 }
38583 else
38584 {
38585 for ( auto& skill : Player::SkillSheet_t::skillSheetData.skillEntries )
38586 {
38587 ++skillsheetIndex;
38588 if ( skill.skillId == skillUp.whichSkill )
38589 {
38590 skillImg->path = skill.skillIconPath;
38591 int currentskill = skillCurrentTxt->isDisabled() ? skillUp.currentSkill : skillUp.currentSkill + skillUp.increaseSkill;
38592 if ( currentskill >= SKILL_LEVEL_LEGENDARY )
38593 {
38594 skillImg->path = skill.skillIconPathLegend;
38595 skillCurrentTxt->setColor(Player::SkillSheet_t::skillSheetData.legendTextColor);
38596 skillCurrentOldTxt->setColor(Player::SkillSheet_t::skillSheetData.legendTextColor);
38597 skillBorderImg->path = Player::SkillSheet_t::skillSheetData.iconBgPathLegend;
38598 skillBgImg->path = "*images/ui/HUD/HUD_SkillUp_BG100_00.png";
38599 }
38600 else if ( currentskill >= SKILL_LEVEL_EXPERT )
38601 {
38602 skillCurrentTxt->setColor(Player::SkillSheet_t::skillSheetData.expertTextColor);
38603 skillCurrentOldTxt->setColor(Player::SkillSheet_t::skillSheetData.expertTextColor);
38604 skillBorderImg->path = Player::SkillSheet_t::skillSheetData.iconBgPathExpert;
38605 skillBgImg->path = "*images/ui/HUD/HUD_SkillUp_BG60_00.png";
38606 }
38607 else if ( currentskill >= SKILL_LEVEL_BASIC )
38608 {
38609 skillCurrentTxt->setColor(Player::SkillSheet_t::skillSheetData.noviceTextColor);
38610 skillCurrentOldTxt->setColor(Player::SkillSheet_t::skillSheetData.noviceTextColor);
38611 skillBorderImg->path = Player::SkillSheet_t::skillSheetData.iconBgPathNovice;
38612 skillBgImg->path = "*images/ui/HUD/HUD_SkillUp_BG20_00.png";
38613 }
38614 else
38615 {
38616 skillCurrentTxt->setColor(Player::SkillSheet_t::skillSheetData.defaultTextColor);
38617 skillCurrentOldTxt->setColor(Player::SkillSheet_t::skillSheetData.defaultTextColor);
38618 skillBorderImg->path = Player::SkillSheet_t::skillSheetData.iconBgPathDefault;
38619 skillBgImg->path = "*images/ui/HUD/HUD_SkillUp_BG_00.png";
38620 }
38621 break;
38622 }
38623 }
38624 }
38625
38626 if ( skillImg->path == "" ) { continue; }
38627
38628 if ( auto imgGet = Image::get(skillImg->path.c_str()) )
38629 {
38630 skillImg->pos.w = imgGet->getWidth();
38631 skillImg->pos.h = imgGet->getHeight();
38632 skillImg->disabled = false;
38633 }
38634 skillImg->pos.x = 140;
38635 if ( !skillUp.isSpell )
38636 {
38637 skillImg->pos.y = 40 - skillImg->pos.h / 2;
38638 }
38639 else
38640 {
38641 skillImg->pos.y = 44 - skillImg->pos.h / 2;
38642 }
38643
38644 if ( auto imgGet = Image::get(skillBorderImg->path.c_str()) )
38645 {
38646 skillBorderImg->pos.w = imgGet->getWidth();
38647 skillBorderImg->pos.h = imgGet->getHeight();
38648 skillBorderImg->disabled = false;
38649 skillBorderImg->pos.x = skillImg->pos.x + skillImg->pos.w / 2 - skillBorderImg->pos.w / 2;
38650 skillBorderImg->pos.y = skillImg->pos.y + skillImg->pos.h / 2 - skillBorderImg->pos.h / 2;
38651 }
38652
38653 auto skillIncreaseTxt = skillFrame->findField("skill increase");
38654 skillIncreaseTxt->setDisabled(skillUp.increaseSkill == 0 || skillUp.isSpell);
38655 if ( !skillIncreaseTxt->isDisabled() )
38656 {
38657 char buf[32] = "";
38658 snprintf(buf, sizeof(buf), "%+d", skillUp.increaseSkill);
38659 skillIncreaseTxt->setText(buf);
38660 if ( auto textGet = skillIncreaseTxt->getTextObject() )
38661 {
38662 SDL_Rect pos;
38663 pos.x = skillImg->pos.x + skillImg->pos.w + 8;
38664 pos.y = 32;
38665 pos.w = std::max(40, (int)textGet->getWidth());
38666 pos.h = std::max(24, (int)textGet->getHeight());
38667
38668 skillIncreaseTxt->setColor(makeColor(255, 255, 255, (1.0 - skillUp.animCurrentStat) * skillUp.animAngle * 255));
38669 pos.x += -*cvar_skillup_angle * cos(PI3.14159265358979323846 / 2 + (3 * skillUp.animAngle * (PI3.14159265358979323846 / 2)));
38670 pos.x += (skillBgImg->pos.w - skillBorderImg->pos.w) * skillUp.animBackground;
38671 pos.y += (*cvar_skillup_angle) - (*cvar_skillup_angle * sin(PI3.14159265358979323846 / 2 + (3 * skillUp.animAngle * (PI3.14159265358979323846 / 2))));
38672 pos.y += *cvar_skillup_increasestatY;
38673 skillIncreaseTxt->setSize(pos);
38674 }
38675 }
38676
38677 if ( skillUp.isSpell )
38678 {
38679 if ( auto spell = getSpellFromID(skillUp.spellID) )
38680 {
38681 std::string spellname = spell->getSpellName();
38682 camelCaseString(spellname);
38683 skillCurrentOldTxt->setText(spellname.c_str());
38684 }
38685 }
38686
38687 if ( auto imgGet = Image::get(skillBgImg->path.c_str()) )
38688 {
38689 skillBgImg->color = makeColor(255, 255, 255, skillUp.animBackground * 255);
38690 skillBgImg->pos.w = imgGet->getWidth();
38691 skillBgImg->pos.h = imgGet->getHeight();
38692 skillBgImg->disabled = false;
38693 skillBgImg->pos.y = skillBorderImg->pos.y;
38694 if ( skillUp.isSpell )
38695 {
38696 skillBgCapImg->path = "#*images/ui/HUD/HUD_NewSpell_BG_Cap_00.png";
38697 if ( auto imgGet2 = Image::get(skillBgCapImg->path.c_str()) )
38698 {
38699 skillBgCapImg->disabled = false;
38700 skillBgCapImg->pos.w = imgGet2->getWidth();
38701 skillBgCapImg->pos.h = imgGet2->getHeight();
38702 skillBgCapImg->pos.y = skillBgImg->pos.y;
38703 skillBgCapImg->color = skillBgImg->color;
38704 }
38705 if ( auto textGet = skillCurrentOldTxt->getTextObject() )
38706 {
38707 skillBgImg->pos.w = (textGet->getWidth() + 8 + skillBgCapImg->pos.w) * skillUp.animBackground;
38708 if ( skillBgImg->pos.w == 0 )
38709 {
38710 skillBgImg->disabled = true;
38711 }
38712 int bgStartX = skillBorderImg->pos.x + skillBorderImg->pos.w - skillBgCapImg->pos.w;
38713 skillBgImg->pos.x = bgStartX;
38714 skillBgCapImg->pos.x = skillBgImg->pos.x + skillBgImg->pos.w;
38715 }
38716 }
38717 else
38718 {
38719 skillBgImg->pos.x = skillBorderImg->pos.x - skillBorderImg->pos.w + skillBorderImg->pos.w * skillUp.animBackground;
38720 }
38721 }
38722
38723 auto skillGleam = skillFrame->findImage("skill gleam");
38724 skillGleam->disabled = !skillUp.isSpell;
38725
38726 skillCurrentOldTxt->setDisabled(true);
38727 if ( skillCurrentTxt->isDisabled() )
38728 {
38729 skillCurrentOldTxt->setDisabled(false);
38730
38731 if ( !skillUp.isSpell )
38732 {
38733 char buf[32] = "";
38734 snprintf(buf, sizeof(buf), "%d", skillUp.currentSkill);
38735 skillCurrentOldTxt->setText(buf);
38736 }
38737 if ( auto textGet = skillCurrentOldTxt->getTextObject() )
38738 {
38739 SDL_Rect pos;
38740 pos.w = textGet->getWidth();
38741 pos.h = std::max(24, (int)textGet->getHeight());
38742 if ( skillUp.isSpell )
38743 {
38744 pos.w = std::min(pos.w, skillBgImg->pos.w);
38745 pos.y = skillBgImg->pos.y + 12;
38746 skillCurrentOldTxt->setFont("fonts/pixel_maz_multiline.ttf#16#2");
38747 pos.x = skillBgCapImg->pos.x - pos.w;
38748 }
38749 else
38750 {
38751 pos.x = skillBgImg->pos.x + skillBgImg->pos.w - pos.w - 12;
38752 pos.y = skillBgImg->pos.y + 8;
38753 skillCurrentOldTxt->setFont("fonts/pixelmix.ttf#16#2");
38754 }
38755 skillCurrentOldTxt->setSize(pos);
38756
38757 Uint8 r, g, b, a;
38758 getColor(skillCurrentOldTxt->getColor(), &r, &g, &b, &a);
38759 skillCurrentOldTxt->setColor(makeColor(r, g, b, skillUp.animBackground * a));
38760 }
38761 }
38762 else if ( auto textGet = skillCurrentTxt->getTextObject() )
38763 {
38764 SDL_Rect pos;
38765 pos.w = textGet->getWidth();
38766 pos.h = textGet->getHeight();
38767 pos.x = skillBgImg->pos.x + skillBgImg->pos.w - pos.w - 12;
38768 pos.y = skillBgImg->pos.y + 8;
38769
38770 if ( skillUp.increaseSkill != 0 )
38771 {
38772 pos.y += -((1.0 - skillUp.animCurrentStat) * *cvar_skillup_falldist);
38773 pos.y += -4 + 4 * (cos(skillUp.animIncreaseStat * 2 * PI3.14159265358979323846));
38774 }
38775 skillCurrentTxt->setSize(pos);
38776
38777 Uint8 r, g, b, a;
38778 getColor(skillCurrentTxt->getColor(), &r, &g, &b, &a);
38779 skillCurrentTxt->setColor(makeColor(r, g, b, skillUp.animCurrentStat * a));
38780 }
38781
38782 skillFramePos.w = std::max(120, (int)(skillBgImg->pos.x + skillBgImg->pos.w + 64));
38783 if ( skillUp.isSpell )
38784 {
38785 skillFramePos.w = std::max(skillFramePos.w, (skillBgCapImg->pos.x + skillBgCapImg->pos.w));
38786 }
38787 skillFrame->setSize(skillFramePos);
38788
38789 if ( skillUp.isSpell )
38790 {
38791 skillPosX.push_back(skillFramePos.x + skillImg->pos.x + skillImg->pos.w / 2 + ((skillBgImg->pos.w) /** skillUp.animBackground*/ / 2));
38792 }
38793 else
38794 {
38795 skillPosX.push_back(skillFramePos.x + skillImg->pos.x + skillImg->pos.w / 2 + ((skillBgImg->pos.w - skillBorderImg->pos.w) * skillUp.animBackground / 2));
38796 }
38797
38798 auto skillNameTxt = skillFrame->findField("skill name txt");
38799 skillNameTxt->setDisabled(true);
38800 {
38801 if ( skillUp.isSpell )
38802 {
38803 skillNameTxt->setText(Language::get(4328));
38804 }
38805 else
38806 {
38807 char buf[128];
38808 snprintf(buf, sizeof(buf), Language::get(4327), Player::SkillSheet_t::skillSheetData.skillEntries[skillsheetIndex].name.c_str());
38809 skillNameTxt->setText(buf);
38810 }
38811 if ( auto textGet = skillNameTxt->getTextObject() )
38812 {
38813 skillNameTxt->setDisabled(false);
38814 SDL_Rect pos = skillNameTxt->getSize();
38815 pos.w = textGet->getWidth();
38816 pos.h = std::max(24, (int)textGet->getHeight());
38817 pos.y = 0;
38818 pos.x = skillPosX[skillPosX.size() - 1] - pos.w / 2;
38819 skillNameTxt->setSize(pos);
38820
38821 Uint8 r, g, b, a;
38822 getColor(0xFFFFFFFF, &r, &g, &b, &a);
38823 skillNameTxt->setColor(makeColor(r, g, b, skillUp.animBackground * a));
38824
38825 skillFramePos.w = std::max(skillFramePos.w, (int)(pos.x + pos.w));
38826 skillFrame->setSize(skillFramePos);
38827 }
38828 }
38829
38830 skillFramePos.x += skillSpacing;
38831
38832 if ( !skillUp.init )
38833 {
38834 skillUp.notificationTargetPosition.x = skillImg->pos.x;
38835 skillUp.notificationTargetPosition.y = skillImg->pos.y;
38836 skillUp.notificationTargetPosition.w = skillImg->pos.w;
38837 skillUp.notificationTargetPosition.h = skillImg->pos.h;
38838 skillUp.baseX = skillImg->pos.x;
38839 skillUp.baseY = skillImg->pos.y;
38840 skillUp.pos.x = skillUp.baseX;
38841 skillUp.pos.y = skillUp.baseY;
38842 skillUp.pos.w = skillImg->pos.w;
38843 skillUp.pos.h = skillImg->pos.h;
38844 skillUp.init = true;
38845
38846 if ( skillUp.isSpell )
38847 {
38848 playSound(*cvar_skill_newspell_sfx, *cvar_skill_sfx_volume);
38849 }
38850 else
38851 {
38852 for ( auto skill : Player::SkillSheet_t::skillSheetData.skillEntries )
38853 {
38854 if ( skill.skillId == skillUp.whichSkill )
38855 {
38856 playSound(skill.skillSfx, *cvar_skill_sfx_volume);
38857 break;
38858 }
38859 }
38860 }
38861 }
38862
38863 if ( skillUp.init )
38864 {
38865 skillUp.animateNotification(player);
38866 skillFrame->setDisabled(false);
38867 }
38868 else
38869 {
38870 skillFrame->setDisabled(true);
38871 }
38872 skillImg->pos = skillUp.pos;
38873 {
38874 int newWidth = skillBorderImg->pos.w * (skillImg->pos.w / (float)skillUp.getIconNominalSize());
38875 int newHeight = skillBorderImg->pos.h * (skillImg->pos.h / (float)skillUp.getIconNominalSize());
38876 skillBorderImg->pos.x -= (newWidth - skillBorderImg->pos.w) / 2;
38877 skillBorderImg->pos.y -= (newHeight - skillBorderImg->pos.h) / 2;
38878 skillBorderImg->pos.w = newWidth;
38879 skillBorderImg->pos.h = newHeight;
38880
38881 if ( skillUp.isSpell )
38882 {
38883 skillGleam->path = "*#images/ui/HUD/HUD_NewSpell_Gleam_00.png";
38884 skillGleam->pos = skillBorderImg->pos;
38885 const int gleam = ((ticks % TICKS_PER_SECOND50) / 5) % 5;
38886 switch ( gleam )
38887 {
38888 case 0:
38889 skillGleam->path = "*#images/ui/HUD/HUD_NewSpell_Gleam_00.png";
38890 break;
38891 case 1:
38892 skillGleam->path = "*#images/ui/HUD/HUD_NewSpell_Gleam_01.png";
38893 break;
38894 case 2:
38895 skillGleam->path = "*#images/ui/HUD/HUD_NewSpell_Gleam_02.png";
38896 break;
38897 case 3:
38898 skillGleam->path = "*#images/ui/HUD/HUD_NewSpell_Gleam_03.png";
38899 break;
38900 case 4:
38901 skillGleam->path = "*#images/ui/HUD/HUD_NewSpell_Gleam_04.png";
38902 break;
38903 default:
38904 break;
38905 }
38906 }
38907 }
38908 skillsFramePos.w = skillFramePos.x + skillFramePos.w;
38909 if ( skillUp.isSpell )
38910 {
38911 skillsFramePos.w = std::max(skillsFramePos.w, skillBgCapImg->pos.x + skillBgCapImg->pos.w);
38912 }
38913 else
38914 {
38915 skillsFramePos.w = std::max(skillsFramePos.w, skillBgImg->pos.x + skillBgImg->pos.w);
38916 }
38917 skillStartX += skillSpacing;
38918 }
38919
38920 int midpoint = 0;
38921 if ( skillPosX.size() > 1 )
38922 {
38923 if ( skillPosX.size() % 2 == 1 )
38924 {
38925 midpoint = skillPosX[size_t(skillPosX.size() / 2)];
38926 }
38927 else
38928 {
38929 size_t midIndex1 = std::max((size_t)0, (skillPosX.size() / 2) - 1);
38930 size_t midIndex2 = (skillPosX.size() / 2);
38931 midpoint = skillPosX[midIndex1] + (skillPosX[midIndex2] - skillPosX[midIndex1]) / 2;
38932 }
38933 skillsFramePos.x -= midpoint;
38934 }
38935 else if ( skillPosX.size() == 1 )
38936 {
38937 skillsFramePos.x -= skillPosX[0];
38938 }
38939 else
38940 {
38941 skillsFramePos.x -= skillsFramePos.w / 2;
38942 }
38943 /*if ( skillsFramePos.x % 2 == 1 )
38944 {
38945 ++skillsFramePos.x;
38946 }*/
38947 skillsFrame->setSize(skillsFramePos);
38948 if ( ticks != skillUp.processedOnTick )
38949 {
38950 skillUp.processedOnTick = ticks;
38951 if ( skillUp.preDelayTicks > 0 )
38952 {
38953 --skillUp.preDelayTicks;
38954 }
38955 else
38956 {
38957 ++skillUp.ticksActive;
38958 }
38959 }
38960
38961 static ConsoleVariable<int> cvar_skillup_framey("/skillup_framey", 48);
38962 int framey = *cvar_skillup_framey;
38963 if ( players[player]->bUseCompactGUIHeight() && players[player]->shootmode )
38964 {
38965 framey -= 28;
38966 framey = std::max(4, framey);
38967 }
38968 SDL_Rect levelUpFramePos{ hud_t.hudFrame->getSize().w / 2 - frameWidth / 2, framey,
38969 frameWidth, skillsFramePos.y + skillsFramePos.h };
38970 levelUpFramePos.x += players[player]->camera_virtualx1();
38971 levelUpFramePos.y += players[player]->camera_virtualy1();
38972 levelUpFramePos.y += skillUp.fadeout * *cvar_skillup_falldist;
38973 if ( players[player]->bUseCompactGUIWidth() || players[player]->bUseCompactGUIHeight() )
38974 {
38975 levelUpFramePos.y += -16;
38976 }
38977 hud_t.skillupFrame->setSize(levelUpFramePos);
38978
38979 if ( skillUp.ticksActive >= skillUp.ticksToLive )
38980 {
38981 skillUp.expired = true;
38982 }
38983}